package ru.yandex.webmaster3.core.proto.converter;

import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.joda.time.Instant;

import ru.yandex.webmaster3.core.sitestructure.DoubleSourcedSiteStructure;
import ru.yandex.webmaster3.core.sitestructure.NewSiteStructure;
import ru.yandex.webmaster3.core.sitestructure.NewSiteStructureNode;
import ru.yandex.webmaster3.core.sitestructure.SearchUrlStatusEnum;
import ru.yandex.webmaster3.core.sitestructure.SearchUrlStatusUtil;
import ru.yandex.webmaster3.core.sitestructure.SiteTreeGenerationId;
import ru.yandex.webmaster3.core.turbo.model.TurboSource;
import ru.yandex.webmaster3.proto.sitestructure.SiteStructureOuterClass;

/**
 * @author avhaliullin
 */
public class SiteStructureConverter {
    private static NewSiteStructureNode convertNodeFromProto(SiteStructureOuterClass.NodeInfo node) {
        Map<Integer, Long> httpCodes = node.getHttpcodesList()
                .stream()
                .collect(Collectors.toMap(SiteStructureOuterClass.HttpCodeInfo::getHttpCode, SiteStructureOuterClass.HttpCodeInfo::getCount));
        Map<SearchUrlStatusEnum, Long> excludedStatuses = node.getExcludedUrlsList()
                .stream()
                .map(info -> Pair.of(SearchUrlStatusUtil.viewFromJupiterStatusId(info.getStatus(), null, false), info.getCount()))
                .filter(pair -> pair.getLeft() != null)
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        Long::sum,
                        () -> new EnumMap<>(SearchUrlStatusEnum.class)
                ));
        Map<TurboSource, Long> turboDocs = node.getTurboSourceInfoList()
                .stream()
                .map(info -> Pair.of(TurboSource.R.fromValue((int) info.getSourceId()), info.getTotal()))
                .filter(pair -> pair.getLeft() != null)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        return new NewSiteStructureNode(node.getNodeId(), node.hasParentId() ? node.getParentId() : null, node.getName(), node.getDocCount(),
                node.getSearchDocCount(), node.getTurboDocCount(), httpCodes, node.getNewUrls(), node.getGoneUrls(), excludedStatuses, turboDocs);
    }

    public static DoubleSourcedSiteStructure convertFromProto(SiteStructureOuterClass.SiteStructure siteStructure,
                                                              SiteTreeGenerationId generationId,
                                                              DateTime realDataCollectionDate) {
        NewSiteStructure curStruct = convertFromProto(
                siteStructure.getCurAutoNodesList(),
                siteStructure.getCurUserNodesList(),
                generationId,
                realDataCollectionDate
        );
        Instant nextBaseDate;
        NewSiteStructure nextBaseStruct;
        if (siteStructure.hasNextBaseDate()) {
            nextBaseDate = new Instant(siteStructure.getNextBaseDate());
            nextBaseStruct = convertFromProto(
                    siteStructure.getNextBaseAutoNodesList(),
                    siteStructure.getNextBaseUserNodesList(),
                    generationId,
                    realDataCollectionDate
            );
        } else {
            nextBaseDate = null;
            nextBaseStruct = null;
        }
        Instant curBaseDate = null;
        if (siteStructure.hasCurBaseDate()) {
            curBaseDate = new Instant(siteStructure.getCurBaseDate());
        }
        return new DoubleSourcedSiteStructure(curStruct, curBaseDate, nextBaseStruct, nextBaseDate);
    }

    public static NewSiteStructure convertFromProto(List<SiteStructureOuterClass.NodeInfo> rawAutoNodes,
                                                    List<SiteStructureOuterClass.NodeInfo> rawUserNodes,
                                                    SiteTreeGenerationId generationId,
                                                    DateTime realDataCollectionDate) {
        List<NewSiteStructureNode> userNodes =
                rawUserNodes
                        .stream()
                        .map(SiteStructureConverter::convertNodeFromProto)
                        .collect(Collectors.toList());
        List<NewSiteStructureNode> autoNodes =
                rawAutoNodes
                        .stream()
                        .map(SiteStructureConverter::convertNodeFromProto)
                        .collect(Collectors.toList());
        return new NewSiteStructure(generationId.getHostId(), generationId.getSiteTreeGenerationId(), realDataCollectionDate, autoNodes, userNodes);
    }

    private static SiteStructureOuterClass.NodeInfo convertNodeToProto(NewSiteStructureNode node) {
        SiteStructureOuterClass.NodeInfo.Builder builder = SiteStructureOuterClass.NodeInfo.newBuilder();
        builder = builder
                .setNodeId(node.getNodeId())
                .setName(node.getName())
                .setDocCount(node.getDocCount())
                .setSearchDocCount(node.getSearchDocCount())
                .setTurboDocCount(node.getTurboDocCount())
                .setNewUrls(node.getNewSearchUrls())
                .setGoneUrls(node.getGoneSearchUrls());
        if (node.getParentNodeId() != null) {
            builder = builder.setParentId(node.getParentNodeId());
        }
        for (Map.Entry<Integer, Long> codeEntry : node.getHttpCodes().entrySet()) {
            SiteStructureOuterClass.HttpCodeInfo codeInfo =
                    SiteStructureOuterClass.HttpCodeInfo.newBuilder()
                            .setHttpCode(codeEntry.getKey())
                            .setCount(codeEntry.getValue())
                            .build();
            builder = builder.addHttpcodes(codeInfo);
        }
        for (Map.Entry<SearchUrlStatusEnum, Long> statusEntry : node.getExcludedStatuses().entrySet()) {
            SiteStructureOuterClass.ExcludedUrlsInfo statusInfo =
                    SiteStructureOuterClass.ExcludedUrlsInfo.newBuilder()
                            .setStatus(SearchUrlStatusUtil.view2Raw(statusEntry.getKey()).value())
                            .setCount(statusEntry.getValue())
                            .build();
            builder = builder.addExcludedUrls(statusInfo);
        }
        for (Map.Entry<TurboSource, Long> entry : node.getSearchTurboDocs().entrySet()) {
            final SiteStructureOuterClass.TurboSourceInfo turboSourceInfo = SiteStructureOuterClass.TurboSourceInfo.newBuilder()
                    .setSourceId(entry.getKey().value())
                    .setTotal(entry.getValue())
                    .build();
            builder.addTurboSourceInfo(turboSourceInfo);
        }
        return builder.build();
    }

    public static SiteStructureOuterClass.SiteStructure convertToProto(DoubleSourcedSiteStructure structure) {
        SiteStructureOuterClass.SiteStructure.Builder builder = SiteStructureOuterClass.SiteStructure.newBuilder();
        for (NewSiteStructureNode node : structure.getCurStructure().getSiteNodes()) {
            builder.addCurAutoNodes(convertNodeToProto(node));
        }
        for (NewSiteStructureNode node : structure.getCurStructure().getUserDefinedNodes()) {
            builder.addCurUserNodes(convertNodeToProto(node));
        }
        builder.setCurBaseDate(structure.getCurBaseDate().getMillis());
        if (structure.getNextBaseDate() != null) {
            builder.setNextBaseDate(structure.getNextBaseDate().getMillis());
            for (NewSiteStructureNode node : structure.getNextBaseStructure().getSiteNodes()) {
                builder.addNextBaseAutoNodes(convertNodeToProto(node));
            }
            for (NewSiteStructureNode node : structure.getNextBaseStructure().getUserDefinedNodes()) {
                builder.addNextBaseUserNodes(convertNodeToProto(node));
            }
        }
        return builder.build();
    }
}
