package ru.yandex.webmaster3.storage.turbo.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Sets;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.Value;
import org.apache.commons.lang3.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.checklist.data.SiteProblemContent;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemState;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemTypeEnum;
import ru.yandex.webmaster3.core.checklist.data.TurboBanSample;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.turbo.model.TurboSampleData;
import ru.yandex.webmaster3.core.turbo.model.ban.TurboBanReason;
import ru.yandex.webmaster3.core.turbo.model.error.TurboErrorType;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedInfo;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedItemStatistics;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedSettings;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedState;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedStatistics;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboFeedType;
import ru.yandex.webmaster3.core.turbo.model.menu.TurboMenuItem;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.core.util.json.JsonMapping;
import ru.yandex.webmaster3.storage.checklist.data.RealTimeSiteProblemInfo;
import ru.yandex.webmaster3.storage.turbo.dao.automorda.TurboAutoMordaStatus;
import ru.yandex.webmaster3.storage.turbo.dao.commerce.model.TurboListingsInfo;
import ru.yandex.webmaster3.storage.turbo.dao.scc.model.TurboSccPremoderationStatus;
import ru.yandex.webmaster3.storage.turbo.service.preview.app.TurboAppReviewsInfo;

import static ru.yandex.webmaster3.storage.turbo.service.TurboDomainsStateService.TurboShopState.State.IN_PROGRESS;
import static ru.yandex.webmaster3.storage.turbo.service.TurboDomainsStateService.TurboShopState.State.NOT_READY;
import static ru.yandex.webmaster3.storage.turbo.service.TurboDomainsStateService.TurboShopState.State.READY;

/**
 * Created by Oleg Bazdyrev on 08/09/2020.
 */
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class TurboDomainsStateService {

    @org.springframework.beans.factory.annotation.Value("${webmaster3.core.turbo.pagePreview.serviceUrl}")
    private String previewServiceUrl;

    public RealTimeSiteProblemInfo createProblemInfo(String domain, SiteProblemTypeEnum problemType, JsonNode rawProblem) {
        WebmasterHostId hostId = IdUtils.urlToHostId(domain);
        SiteProblemContent content = createProblemContentFromRawData(problemType, rawProblem.get("data"));
        DateTime actualSince = new DateTime(rawProblem.get("actual_since").asLong());
        DateTime lastUpdate = new DateTime(rawProblem.get("last_update").asLong());
        return new RealTimeSiteProblemInfo(hostId, lastUpdate, actualSince, lastUpdate, SiteProblemState.PRESENT, problemType, content, 0);
    }

    public SiteProblemContent createProblemContentFromRawData(SiteProblemTypeEnum problemType, JsonNode data) {
        switch (problemType) {
            case TURBO_RSS_ERROR:
                return new SiteProblemContent.TurboRssError(feedInfosFromRawData(data));
            case TURBO_RSS_WARNING:
                return new SiteProblemContent.TurboRssWarning(feedInfosFromRawData(data));
            case TURBO_YML_ERROR:
                return new SiteProblemContent.TurboYmlError(feedInfosFromRawData(data));
            case TURBO_YML_WARNING:
                return new SiteProblemContent.TurboYmlWarning(feedInfosFromRawData(data));
            case TURBO_LISTING_ERROR:
                return new SiteProblemContent.TurboListingError();
            case TURBO_FEED_BAN:
            case TURBO_DOCUMENT_BAN:
                return createBanProblemContent(problemType, data);
            default:
                throw new IllegalStateException("Unsupported problem type: " + problemType);
        }
    }

    private List<TurboFeedInfo> feedInfosFromRawData(JsonNode data) {
        List<TurboFeedRawState> badRawFeeds = JsonMapping.readValue(data.traverse(), TurboFeedRawState.LIST_REFERENCE);
        return badRawFeeds.stream().map(rawFeed -> rawFeed.toFeedInfo(previewServiceUrl)).collect(Collectors.toList());
    }

    private static final Set<String> BANNED_BY_DOMAIN = Sets.newHashSet("feed_domain", "doc_domain");

    private SiteProblemContent createBanProblemContent(SiteProblemTypeEnum type, JsonNode data) {
        List<TurboBanRawInfo> bans = JsonMapping.readValue(data.traverse(), TurboBanRawInfo.LIST_REFERENCE);
        // соберем причины банов и минимальную дату банов
        Set<TurboBanReason> reasons = EnumSet.noneOf(TurboBanReason.class);
        List<TurboBanSample> samples = new ArrayList<>();

        DateTime applyDate = null;
        boolean bannedByDomain = false;
        for (TurboBanRawInfo ban : bans) {
            TurboBanReason reason = TurboBanReason.byCode(ban.getReason());
            TurboErrorType error = TurboErrorType.fromCode(ban.getError());
            reasons.add(reason);
            applyDate = ObjectUtils.min(applyDate, ban.getApplyDate());
            bannedByDomain |= BANNED_BY_DOMAIN.contains(ban.getBanType());
            for (TurboBanSampleRaw sampleRaw : ban.getSamples()) {
                samples.add(new TurboBanSample(sampleRaw.getUrl(), reason, error));
            }
        }

        SiteProblemContent.TurboBanContent result;
        switch (type) {
            case TURBO_FEED_BAN: {
                List<String> feeds = bans.stream().filter(row -> !BANNED_BY_DOMAIN.contains(row.getBanType()))
                        .map(TurboBanRawInfo::getMarker).collect(Collectors.toList());
                result = new SiteProblemContent.TurboFeedBan(bannedByDomain, reasons, applyDate, feeds);
                break;
            }
            case TURBO_DOCUMENT_BAN: {
                List<String> feeds = bans.stream().flatMap(row -> row.getSamples().stream())
                        .map(TurboBanSampleRaw::getFeed).filter(Objects::nonNull).distinct().collect(Collectors.toList());
                result = new SiteProblemContent.TurboDocumentBan(bannedByDomain, reasons, applyDate, bans.size(), feeds);
                break;
            }
            default:
                throw new IllegalStateException();
        }
        result.getSamples().addAll(samples);
        return result;
    }

    public TurboFeedStatistics feedStatisticsFromRawState(TurboFeedRawState rawState) {
        return rawState.toFeedStatistics(previewServiceUrl);
    }

    @Value
    @AllArgsConstructor(onConstructor_ = @Autowired)
    public static class TurboFeedRawState {
        public static final TypeReference<List<TurboFeedRawState>> LIST_REFERENCE = new TypeReference<>() {
        };

        @JsonProperty("domain")
        String domain;
        @JsonProperty("feed")
        String feed;
        @JsonProperty("type")
        String type;
        @JsonProperty("active")
        boolean active;
        @JsonProperty("timestamp")
        long timestamp;
        @JsonProperty("last_access")
        long lastAccess;
        @JsonProperty("data")
        TurboFeedRawStatsData data;

        public TurboFeedInfo toFeedInfo(String previewServiceUrl) {
            DateTime updateDate = new DateTime(TimeUnit.SECONDS.toMillis(getTimestamp()));
            DateTime downloadDate = getLastAccess() == 0L ? null : new DateTime(TimeUnit.SECONDS.toMillis(getLastAccess()));
            TurboFeedSettings settings = TurboFeedSettings.internalFeed(getDomain(), TurboFeedType.byCode(getType()), getFeed(),
                    active, TurboFeedState.DOWNLOADING, null, updateDate, null, null, null, null, null, null);

            TurboFeedStatistics statistics = toFeedStatistics(previewServiceUrl);

            return new TurboFeedInfo(settings, statistics, null);
        }

        @NotNull
        private TurboFeedStatistics toFeedStatistics(String previewServiceUrl) {
            DateTime updateDate = new DateTime(TimeUnit.SECONDS.toMillis(getTimestamp()));
            DateTime downloadDate = getLastAccess() == 0L ? null : new DateTime(TimeUnit.SECONDS.toMillis(getLastAccess()));
            return new TurboFeedStatistics(
                    TurboFeedType.byCode(getType()), getFeed(), active,
                    getData().collectTurboUrls(previewServiceUrl, true),
                    updateDate,
                    downloadDate,
                    getData().collectTurboRawErrors(),
                    TurboFeedItemStatistics.fromObjectNode(getData().getStats()),
                    getData().getHash());
        }
    }

    @Value
    @AllArgsConstructor(onConstructor_ = @Autowired)
    public static final class TurboBanRawInfo {
        public static final TypeReference<List<TurboBanRawInfo>> LIST_REFERENCE = new TypeReference<>() {
        };

        @JsonProperty("domain")
        String domain;
        @JsonProperty("marker")
        String marker;
        @JsonProperty("ban_type")
        String banType;
        @JsonProperty("ban_reason")
        String reason;
        @JsonProperty("timestamp")
        Long timestamp;
        @JsonProperty("apply_timestamp")
        Long applyTimestamp;
        @JsonProperty("ban_error")
        String error;
        @JsonProperty("ban_samples")
        List<TurboBanSampleRaw> samples;

        @JsonIgnore
        public DateTime getApplyDate() {
            return applyTimestamp == null || applyTimestamp == 0L ? null : new DateTime(applyTimestamp * 1000L);
        }
    }

    @Value
    @AllArgsConstructor(onConstructor_ = @Autowired)
    public static final class TurboBanSampleRaw {
        @JsonProperty("document")
        String url;
        @JsonProperty("feed")
        String feed;
    }

    @Value
    @ToString
    public static final class TurboCategoryInfoRaw {
        public static final TypeReference<List<TurboCategoryInfoRaw>> LIST_REFERENCE = new TypeReference<>() {
        };
        @JsonProperty("id")
        Long id;
        @JsonProperty("label")
        String label;
        @JsonProperty("original_url")
        String originalUrl;
        @JsonProperty("turbo_url")
        String turboUrl;
        @JsonProperty("order")
        Long order;
        @JsonProperty("parent_id")
        Long parentId;
    }

    @Value
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static final class TurboMarketFeed {
        public static final TypeReference<List<TurboMarketFeed>> LIST_REFERENCE = new TypeReference<>() {
        };
        @JsonProperty("domain")
        String domain;
        @JsonProperty("feed_id")
        long feedId;
        @JsonProperty("partner_id")
        long partnerId;
        @JsonProperty("url")
        String url;
    }

    @Value
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class TurboBannedScc {
        public static final TypeReference<TurboBannedScc> TYPE_REFERENCE = new TypeReference<>() {
        };
        private static final TurboBannedScc EMPTY = new TurboBannedScc(null);
        @JsonProperty("comment")
        String comment;

        public static TurboBannedScc empty() {
            return EMPTY;
        }

        public boolean isBanned() {
            return comment != null;
        }
    }

    @Value
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class TurboShopState {
        public static final TypeReference<TurboShopState> TYPE_REFERENCE = new TypeReference<>() {
        };
        private static final TurboShopState EMPTY = new TurboShopState(NOT_READY);

        public enum State{
            @JsonEnumDefaultValue
            NOT_READY,
            IN_PROGRESS,
            READY
        }

        @JsonProperty(value = "state")
        State state;

        public boolean isReady() {
            return state == READY;
        }

        public boolean isInProgress() {
            return state == IN_PROGRESS;
        }

        public boolean isNotReady() {
            return state == null || state == NOT_READY;
        }

        public static TurboShopState empty() {
            return EMPTY;
        }
    }

    @Value
    @Builder
    public static class TurboDomainState {
        public static final TypeReference<Map<SiteProblemTypeEnum, JsonNode>> PROBLEMS_REFERENCE = new TypeReference<>() {
        };
        String domain;
        List<TurboFeedStatistics> rssFeeds;
        List<TurboFeedStatistics> ymlFeeds;
        List<TurboBanRawInfo> bans;
        List<TurboSampleData> autorelatedSamples;
        List<TurboSampleData> automordaSamples;
        TurboAutoMordaStatus autoMordaStatus;
        TurboAppReviewsInfo appReviewsInfo;
        List<TurboSampleData> autoparserSamples;
        List<TurboMenuItem> commerceCategories;
        String experiment;
        TurboListingsInfo listingsInfo;
        List<TurboMarketFeed> marketFeeds;
        TurboSccPremoderationStatus premoderationResult;
        TurboBannedScc bannedScc;
        TurboShopState shopState;
        List<RealTimeSiteProblemInfo> problems;

        /**
         * default empty value instead of Optional
         *
         * @param domain
         * @return
         */
        public static TurboDomainState empty(String domain) {
            return TurboDomainState.builder().domain(domain)
                    .rssFeeds(Collections.emptyList())
                    .ymlFeeds(Collections.emptyList())
                    .bans(Collections.emptyList())
                    .autorelatedSamples(Collections.emptyList())
                    .automordaSamples(Collections.emptyList())
                    .autoMordaStatus(TurboAutoMordaStatus.NOT_ENOUGH_PAGES)
                    .appReviewsInfo(TurboAppReviewsInfo.empty(domain))
                    .autoparserSamples(Collections.emptyList())
                    .commerceCategories(Collections.emptyList())
                    .marketFeeds(Collections.emptyList())
                    .premoderationResult(TurboSccPremoderationStatus.empty())
                    .bannedScc(TurboBannedScc.empty())
                    .shopState(TurboShopState.empty())
                    .problems(Collections.emptyList())
                    .build();
        }

        @JsonIgnore
        public TurboAppReviewsInfo getAppReviewsInfoOrDefault() {
            return appReviewsInfo == null ? TurboAppReviewsInfo.empty(domain) : appReviewsInfo.setDefaults(domain);
        }
    }


}
