package ru.yandex.webmaster3.core.checklist.data;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.google.common.collect.Sets;
import org.apache.commons.collections4.CollectionUtils;

import ru.yandex.webmaster3.core.util.enums.IntEnum;
import ru.yandex.webmaster3.core.util.enums.IntEnumResolver;
import ru.yandex.webmaster3.core.util.json.polymorphic.Discriminator;

import static ru.yandex.webmaster3.core.checklist.data.SiteProblemSeverityEnum.CRITICAL;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemSeverityEnum.FATAL;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemSeverityEnum.POSSIBLE_PROBLEM;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemSeverityEnum.RECOMMENDATION;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemState.ABSENT;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemState.NOT_APPLICABLE;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemState.PRESENT;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemState.UNDEFINED;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.FAVICON_PROBLEM;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.GENERATION_BASED;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.HOST_PROBLEM;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.REAL_TIME;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.REAL_TIME_DOMAIN;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.REAL_TIME_DOMAIN_WITH_M;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.REAL_TIME_OWNER;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.SITEMAP_PROBLEM;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.THREATS_PROBLEM;
import static ru.yandex.webmaster3.core.checklist.data.SiteProblemStorageType.TURBO_PROBLEM;

/**
 * @author avhaliullin
 */
public enum SiteProblemTypeEnum implements IntEnum, Discriminator<SiteProblemContent> {
    SANCTIONS(2, false, SiteProblemContent.SanctionsProblem.class, FATAL, GENERATION_BASED), // disabled
    SECURITY(3, false, SiteProblemContent.SecurityProblem.class, FATAL, GENERATION_BASED), // disabled
    SITEMAP_NOT_SET(4, true, SiteProblemContent.SitemapNotSet.class, POSSIBLE_PROBLEM, SITEMAP_PROBLEM),
    ERRORS_IN_SITEMAPS(5, true, SiteProblemContent.ErrorsInSitemaps.class, POSSIBLE_PROBLEM, SITEMAP_PROBLEM),
    NO_REGIONS(6, true, SiteProblemContent.NoRegions.class, RECOMMENDATION, HOST_PROBLEM),
    NO_DICTIONARY_REGIONS(7, true, SiteProblemContent.NoDictionaryRegions.class, RECOMMENDATION, HOST_PROBLEM),
    NO_SERPLINKS(8, true, SiteProblemContent.NoSerpLinks.class, RECOMMENDATION, REAL_TIME), // disabled
    DISALLOWED_IN_ROBOTS(9, false, SiteProblemContent.DisallowedInRobots.class, FATAL, HOST_PROBLEM),
    DNS_ERROR(10, false, SiteProblemContent.DNSError.class, FATAL, HOST_PROBLEM),
    NO_404_ERRORS(11, true, SiteProblemContent.No404Errors.class, POSSIBLE_PROBLEM, REAL_TIME),
    ROBOTS_TXT_ERROR(12, true, SiteProblemContent.RobotsTxtError.class, POSSIBLE_PROBLEM, REAL_TIME),
    DOMAIN_EXPIRES(13, false, SiteProblemContent.DomainNameExpires.class, CRITICAL, REAL_TIME, UNDEFINED, state -> state == PRESENT), // disabled
    MORDA_ERROR(14, false, SiteProblemContent.MordaError.class, FATAL, REAL_TIME),
    MORDA_REDIRECTS(15, true, SiteProblemContent.MordaRedirects.class, POSSIBLE_PROBLEM, REAL_TIME),
    MISSING_FAVICON(16, true, SiteProblemContent.MissingFavicon.class, RECOMMENDATION, FAVICON_PROBLEM),
    TOO_MANY_URL_DUPLICATES(17, true, SiteProblemContent.TooManyUrlDuplicates.class, POSSIBLE_PROBLEM, GENERATION_BASED), // disabled
    NO_ROBOTS_TXT(18, true, SiteProblemContent.NoRobotsTxt.class, POSSIBLE_PROBLEM, REAL_TIME),
    NO_HOST_DIRECTIVE(19, true, SiteProblemContent.NoHostDirective.class, POSSIBLE_PROBLEM, REAL_TIME), // disabled
    AMBIGUOUS_HOST_DIRECTIVES(20, true, SiteProblemContent.AmbiguousHostDirectives.class, POSSIBLE_PROBLEM, REAL_TIME), // disabled
    DOCUMENTS_MISSING_TITLE(21, true, SiteProblemContent.DocumentsMissingTitle.class, POSSIBLE_PROBLEM, HOST_PROBLEM),
    DOCUMENTS_MISSING_DESCRIPTION(22, true, SiteProblemContent.DocumentsMissingDescription.class, POSSIBLE_PROBLEM, HOST_PROBLEM),
    //    MICRODATA_ERRORS(23, SiteProblemSeverityEnum.POSSIBLE_PROBLEM, SiteProblemStorageType.REAL_TIME),
    NO_SITEMAP_MODIFICATIONS(24, true, SiteProblemContent.NoSitemapModifications.class, POSSIBLE_PROBLEM, SITEMAP_PROBLEM),
    TOO_MANY_BROKEN_LINKS(25, true, SiteProblemContent.TooManyBrokenLinks.class, CRITICAL, GENERATION_BASED), // disabled
    NOT_MOBILE_FRIENDLY(26, true, SiteProblemContent.NotMobileFriendly.class, RECOMMENDATION, REAL_TIME, UNDEFINED, state -> state != ABSENT),
    @Deprecated // актуальный тип SLOW_AVG_RESPONSE_WITH_EXAMPLES
    SLOW_AVG_RESPONSE(27, false, SiteProblemContent.SlowResponse.class, CRITICAL, REAL_TIME), // disabled
    THREATS(28, false, SiteProblemContent.Threats.class, FATAL, THREATS_PROBLEM),
    NO_METRIKA_COUNTER(29, false, SiteProblemContent.NoMetrikaCounter.class, RECOMMENDATION, REAL_TIME_DOMAIN_WITH_M, ABSENT, ign -> false),
    TURBO_ERROR(30, false, SiteProblemContent.TurboError.class, CRITICAL, REAL_TIME_DOMAIN, ABSENT, ign -> false), // disabled
    TURBO_WARNING(31, false, SiteProblemContent.TurboWarning.class, POSSIBLE_PROBLEM, REAL_TIME_DOMAIN, ABSENT, ign -> false), // disabled
    YABROWSER_BADAD(32, false, SiteProblemContent.YaBrowserBadAd.class, POSSIBLE_PROBLEM, REAL_TIME), // disabled
    TURBO_FEED_BAN(33, false, SiteProblemContent.TurboFeedBan.class, FATAL, TURBO_PROBLEM, ABSENT, ign -> false),
    TURBO_DOCUMENT_BAN(34, false, SiteProblemContent.TurboDocumentBan.class, FATAL, TURBO_PROBLEM, ABSENT, ign -> false), // disabled
    TURBO_HOST_BAN(35, false, SiteProblemContent.TurboHostBan.class, CRITICAL, REAL_TIME_OWNER),
    TURBO_INSUFFICIENT_CLICKS_SHARE(37, false, SiteProblemContent.TurboInsufficientClicksShare.class, RECOMMENDATION, REAL_TIME_DOMAIN, ABSENT, state -> false), // disabled
    NO_CHATS(38, true, SiteProblemContent.NoChats.class, RECOMMENDATION, REAL_TIME, NOT_APPLICABLE, ign -> false), // disabled
    HOST_COMPANY_PROFILE_NOT_FILLED(39, false, SiteProblemContent.HostCompanyProfileNotFilled.class, RECOMMENDATION, HOST_PROBLEM, ABSENT, ign -> false), // disabled
    TOO_MANY_DOMAINS_ON_SEARCH(40, false, SiteProblemContent.TooManyDomainsOnSearch.class, POSSIBLE_PROBLEM, REAL_TIME, ABSENT, ign -> false),
    SSL_CERTIFICATE_ERROR(41, false, SiteProblemContent.SslCertificateError.class, CRITICAL, REAL_TIME, ABSENT, ign -> false),
    MAIN_MIRROR_IS_NOT_HTTPS(42, true, SiteProblemContent.MainMirrorIsNotHttps.class, POSSIBLE_PROBLEM, REAL_TIME, ABSENT, ign -> false),
    HOST_COMPANY_PROFILE_CREATED(43, true, SiteProblemContent.HostCompanyProfileCreated.class, RECOMMENDATION, HOST_PROBLEM, ABSENT, ign -> false),
    FAVICON_ERROR(44, false, SiteProblemContent.FaviconError.class, POSSIBLE_PROBLEM, FAVICON_PROBLEM, ABSENT, ign -> false),
    TURBO_LISTING_ERROR(45, false, SiteProblemContent.TurboListingError.class, POSSIBLE_PROBLEM, TURBO_PROBLEM, ABSENT, ign -> false),
    NO_METRIKA_COUNTER_CRAWL_ENABLED(46, false, SiteProblemContent.NoMetrikaCounterCrawlEnabled.class, POSSIBLE_PROBLEM, REAL_TIME_DOMAIN_WITH_M, ABSENT, ign -> false),
    TURBO_URL_ERRORS(47, false, SiteProblemContent.TurboUrlErrors.class, CRITICAL, REAL_TIME_DOMAIN, ABSENT, ign -> false),
    SLOW_AVG_RESPONSE_WITH_EXAMPLES(48, false, SiteProblemContent.SlowResponseWithExamples.class, CRITICAL, HOST_PROBLEM),
    NO_METRIKA_COUNTER_BINDING(49, false, SiteProblemContent.NoMetrikaCounterBinding.class, POSSIBLE_PROBLEM, REAL_TIME_DOMAIN_WITH_M, ABSENT, ign -> false),
    TURBO_INVALID_CART_URL(50, false, SiteProblemContent.TurboInvalidCartUrl.class, CRITICAL, REAL_TIME_DOMAIN),
    BIG_FAVICON_ABSENT(51, true, SiteProblemContent.BigFaviconAbsent.class, RECOMMENDATION, FAVICON_PROBLEM, ABSENT, ign -> false),
    CONNECT_FAILED(52, false, SiteProblemContent.ConnectFailed.class, FATAL, HOST_PROBLEM),
    TURBO_RSS_ERROR(60, false, SiteProblemContent.TurboRssError.class, CRITICAL, TURBO_PROBLEM, ABSENT, ign -> false),
    TURBO_RSS_WARNING(61, false, SiteProblemContent.TurboRssWarning.class, POSSIBLE_PROBLEM, TURBO_PROBLEM, ABSENT, ign -> false),
    TURBO_YML_ERROR(62, false, SiteProblemContent.TurboYmlError.class, CRITICAL, TURBO_PROBLEM, ABSENT, ign -> false),
    TURBO_YML_WARNING(63, false, SiteProblemContent.TurboYmlWarning.class, POSSIBLE_PROBLEM, TURBO_PROBLEM, ABSENT, ign -> false),
    TURBO_HOST_BAN_INFO(64, false, SiteProblemContent.TurboHostBanInfo.class, POSSIBLE_PROBLEM, REAL_TIME_OWNER),
    //костыль чтобы выдавать плашку про то, что с хостом все хорошо. Не захотел отдельно создавать табличку и прописывать этот же сценарий не для проблем, а для положительных меток
    TURBO_HOST_BAN_OK(65, false, SiteProblemContent.TurboHostBanOk.class, RECOMMENDATION, REAL_TIME_OWNER), // disabled
    INSIGNIFICANT_CGI_PARAMETER(66, true, SiteProblemContent.InsignificantCGIParameter.class, CRITICAL, REAL_TIME),
    DUPLICATE_CONTENT_ATTRS(67, false, SiteProblemContent.DuplicateContentAttrs.class, POSSIBLE_PROBLEM, HOST_PROBLEM),
    DUPLICATE_PAGES(68, false, SiteProblemContent.DuplicatePages.class, POSSIBLE_PROBLEM, HOST_PROBLEM),
    URL_ALERT_4XX(69, true, SiteProblemContent.UrlAlert4xx.class, CRITICAL, REAL_TIME, ABSENT, ign -> false),
    URL_ALERT_5XX(70, true, SiteProblemContent.UrlAlert5xx.class, CRITICAL, REAL_TIME, ABSENT, ign -> false),
    DISALLOWED_URLS_ALERT(71, false, SiteProblemContent.DisallowedUrlsAlert.class, POSSIBLE_PROBLEM, REAL_TIME),
    VIDEOHOST_OFFER_FAILED(72, false, SiteProblemContent.VideohostOfferFailed.class, POSSIBLE_PROBLEM, REAL_TIME_DOMAIN),
    VIDEOHOST_OFFER_IS_NEEDED(73, false, SiteProblemContent.VideohostOfferIsNeeded.class, POSSIBLE_PROBLEM, REAL_TIME_DOMAIN),
    VIDEOHOST_OFFER_NEED_PAPER(74, false, SiteProblemContent.VideohostOfferIsNeeded.class, POSSIBLE_PROBLEM, REAL_TIME_DOMAIN),
    ;

    private final int value;
    private final boolean onlyForMainMirror;
    private final Class<? extends SiteProblemContent> dataClass;
    private final SiteProblemSeverityEnum severity;
    private final SiteProblemStorageType storageType;
    private final SiteProblemState defaultState;
    private final Predicate<SiteProblemState> recheckablePredicate;

    SiteProblemTypeEnum(int value, boolean onlyForMainMirror, Class<? extends SiteProblemContent> dataClass,
                        SiteProblemSeverityEnum severity, SiteProblemStorageType storageType) {
        this(value, onlyForMainMirror, dataClass, severity, storageType, ABSENT, state -> state == PRESENT);
    }

    SiteProblemTypeEnum(int value, boolean onlyForMainMirror, Class<? extends SiteProblemContent> dataClass,
                        SiteProblemSeverityEnum severity, SiteProblemStorageType storageType,
                        SiteProblemState defaultState, Predicate<SiteProblemState> recheckablePredicate) {
        this.value = value;
        this.onlyForMainMirror = onlyForMainMirror;
        this.dataClass = dataClass;
        this.severity = severity;
        this.storageType = storageType;
        this.defaultState = defaultState;
        this.recheckablePredicate = recheckablePredicate;
    }

    public SiteProblemSeverityEnum getSeverity() {
        return severity;
    }

    public boolean isCriticalSeverity() {
        return severity == SiteProblemSeverityEnum.CRITICAL ||
                severity == SiteProblemSeverityEnum.FATAL;
    }

    public boolean isRecheckable(SiteProblemState state) {
        return recheckablePredicate.test(state);
    }

    public boolean isRecheckable(SiteProblemState state, SiteProblemContent content) {
        return recheckablePredicate.test(state) && (content == null || content.isRecheckable());
    }

    @Override
    public int value() {
        return value;
    }

    /**
     * Применим ли данный тип проблемы только для главных зеркал
     *
     * @return
     */
    public boolean isOnlyForMainMirror() {
        return onlyForMainMirror;
    }

    public boolean isApplicableForNonMainMirror() {
        return !onlyForMainMirror;
    }

    @Override
    public Class<? extends SiteProblemContent> getDataClass() {
        return dataClass;
    }

    public boolean isDisabled() {
        return DISABLED_PROBLEMS.contains(this);
    }

    public SiteProblemStorageType getStorageType() {
        return storageType;
    }

    public SiteProblemState getDefaultState() {
        return defaultState;
    }

    public boolean isTurboProblem() {
        return (this.name().contains("TURBO"));
    }

    public boolean isTurboShopProblem(boolean hasTurboPages) {
        return TURBO_SHOP_PROBLEMS.contains(this) && (hasTurboPages || !TURBO_COMMON_PROBLEMS.contains(this));
    }

    public boolean isTurboContentProblem(boolean hasTurboPages) {
        return TURBO_CONTENT_PROBLEMS.contains(this) && (hasTurboPages || !TURBO_COMMON_PROBLEMS.contains(this));
    }

    public static final IntEnumResolver<SiteProblemTypeEnum> R = IntEnumResolver.r(SiteProblemTypeEnum.class);

    public static final Set<SiteProblemTypeEnum> DISABLED_PROBLEMS = Collections.unmodifiableSet(
            EnumSet.of(DOMAIN_EXPIRES, SANCTIONS, SECURITY, NO_HOST_DIRECTIVE, AMBIGUOUS_HOST_DIRECTIVES, NO_SERPLINKS,
                    HOST_COMPANY_PROFILE_NOT_FILLED, YABROWSER_BADAD, SLOW_AVG_RESPONSE,
                    TURBO_ERROR, TURBO_WARNING, NO_CHATS, TURBO_DOCUMENT_BAN, TURBO_HOST_BAN_OK, TOO_MANY_URL_DUPLICATES, TOO_MANY_BROKEN_LINKS,
                    TURBO_INSUFFICIENT_CLICKS_SHARE)
    );

    public static final Set<SiteProblemTypeEnum> FORGETTABLE_PROBLEMS = Collections.unmodifiableSet(
            EnumSet.of(NO_SITEMAP_MODIFICATIONS, ERRORS_IN_SITEMAPS, SITEMAP_NOT_SET, NO_REGIONS, NO_DICTIONARY_REGIONS)
    );

    public static final Set<SiteProblemTypeEnum> ENABLED_PROBLEMS = Collections.unmodifiableSet(
            Sets.complementOf(DISABLED_PROBLEMS)
    );

    public static final Set<SiteProblemTypeEnum> TURBO_PROBLEMS = Stream.of(SiteProblemTypeEnum.values())
            .filter(SiteProblemTypeEnum::isTurboProblem).collect(Collectors.toUnmodifiableSet());

    public static final Set<SiteProblemTypeEnum> TURBO_CONTENT_PROBLEMS = Collections.unmodifiableSet(EnumSet.of(
            TURBO_FEED_BAN, TURBO_HOST_BAN, TURBO_HOST_BAN_INFO, TURBO_DOCUMENT_BAN, TURBO_URL_ERRORS, TURBO_RSS_ERROR, TURBO_RSS_WARNING
    ));

    public static final Set<SiteProblemTypeEnum> TURBO_SHOP_PROBLEMS = Collections.unmodifiableSet(EnumSet.of(
            TURBO_FEED_BAN, TURBO_DOCUMENT_BAN, TURBO_URL_ERRORS, TURBO_YML_ERROR, TURBO_YML_WARNING,
            TURBO_LISTING_ERROR, TURBO_INVALID_CART_URL
    ));

    public static final Set<SiteProblemTypeEnum> TURBO_COMMON_PROBLEMS = Collections.unmodifiableSet(EnumSet.copyOf(
            CollectionUtils.intersection(TURBO_CONTENT_PROBLEMS, TURBO_SHOP_PROBLEMS)));

    // TODO GENERATION_BASED-проблемы БЕЗ угроз
    public static final Set<SiteProblemTypeEnum> GENERATION_BASED_PROBLEMS = Stream.of(SiteProblemTypeEnum.values())
            .filter(type -> type.getStorageType() == GENERATION_BASED && type != THREATS)
            .filter(type -> !type.isDisabled())
            .collect(Collectors.toUnmodifiableSet());

    // противоположность GENERATION_BASED_PROBLEMS
    public static final Set<SiteProblemTypeEnum> NON_GENERATION_BASED_PROBLEMS = Stream.of(SiteProblemTypeEnum.values())
            .filter(type -> type.getStorageType() != GENERATION_BASED || type == THREATS)
            .filter(type -> !type.isDisabled())
            .collect(Collectors.toUnmodifiableSet());


    /**
     * @return "разрешённые" (т.е. isDisabled() == false) инстансы, имена которых содержатся в параметре {@code names}
     */
    public static Set<SiteProblemTypeEnum> fromStrings(Collection<String> names) {
        Set<String> set = new HashSet<>(names);
        return Arrays.stream(values())
                .filter(type -> set.contains(type.name()))
                .filter(type -> !type.isDisabled())
                .collect(Collectors.toCollection(() -> EnumSet.noneOf(SiteProblemTypeEnum.class)));
    }

}
