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

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemContent;
import ru.yandex.webmaster3.core.checklist.data.SiteProblemTypeEnum;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.http.WebmasterJsonModule;
import ru.yandex.wmtools.common.util.http.YandexHttpStatus;

/**
 * @author avhaliullin
 */
public class SiteProblemContentSerializer {
    private static final Logger log = LoggerFactory.getLogger(SiteProblemContentSerializer.class);

    private static final ObjectMapper OM = new ObjectMapper()
            .registerModule(new JodaModule())
            .registerModule(new ParameterNamesModule())
            .registerModule(new WebmasterJsonModule(true))
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

    public static SiteProblemContent deserialize(SiteProblemTypeEnum problemType, String content) {
        try {
            switch (problemType) {
                case SANCTIONS:
                    if (content == null) {
                        return new SiteProblemContent.SanctionsProblem(null);
                    } else {
                        return OM.readValue(content, SiteProblemContent.SanctionsProblem.class);
                    }
                case SECURITY:
                    return new SiteProblemContent.SecurityProblem();
                case SITEMAP_NOT_SET:
                    return new SiteProblemContent.SitemapNotSet();
                case ERRORS_IN_SITEMAPS:
                    return OM.readValue(content, SiteProblemContent.ErrorsInSitemaps.class);
                case NO_REGIONS:
                    return new SiteProblemContent.NoRegions();
                case NO_DICTIONARY_REGIONS:
                    return new SiteProblemContent.NoDictionaryRegions();
                case NO_SERPLINKS:
                    return new SiteProblemContent.NoSerpLinks();
                case DISALLOWED_IN_ROBOTS:
                    return new SiteProblemContent.DisallowedInRobots();
                case DNS_ERROR:
                    return new SiteProblemContent.DNSError();
                case CONNECT_FAILED:
                    return new SiteProblemContent.ConnectFailed();
                case NO_404_ERRORS:
                    return new SiteProblemContent.No404Errors();
                case ROBOTS_TXT_ERROR:
                    return new SiteProblemContent.RobotsTxtError();
                case DOMAIN_EXPIRES:
                    return OM.readValue(content, SiteProblemContent.DomainNameExpires.class);
                case MORDA_ERROR:
                    MordaErrorSerializationAdapter adapter = OM.readValue(content, MordaErrorSerializationAdapter.class);
                    return new SiteProblemContent.MordaError(
                            adapter.lastAccess,
                            adapter.extendedStatus == null ? SiteProblemContent.MordaError.ExtendedStatus.DEFAULT : SiteProblemContent.MordaError.ExtendedStatus.R.fromValue(adapter.extendedStatus),
                            adapter.httpStatusCode == null ? null : YandexHttpStatus.parseCode(adapter.httpStatusCode),
                            adapter.followedRedirect
                    );
                case MORDA_REDIRECTS:
                    return content == null ? null : OM.readValue(content, SiteProblemContent.MordaRedirects.class);
                case MISSING_FAVICON:
                    return new SiteProblemContent.MissingFavicon();
                case TOO_MANY_URL_DUPLICATES:
                    return content == null ? null : OM.readValue(content, SiteProblemContent.TooManyUrlDuplicates.class);
                case NO_ROBOTS_TXT:
                    return new SiteProblemContent.NoRobotsTxt();
                case NO_HOST_DIRECTIVE:
                    return new SiteProblemContent.NoHostDirective();
                case AMBIGUOUS_HOST_DIRECTIVES:
                    return new SiteProblemContent.AmbiguousHostDirectives();
//                case MICRODATA_ERRORS:
//                    return new SiteProblemContent.MicrodataErrors();
                case NO_SITEMAP_MODIFICATIONS:
                    return content == null ? null : OM.readValue(content, SiteProblemContent.NoSitemapModifications.class);
                case TOO_MANY_BROKEN_LINKS:
                    return new SiteProblemContent.TooManyBrokenLinks();
                case NOT_MOBILE_FRIENDLY:
                    return content == null ? null : OM.readValue(content, SiteProblemContent.NotMobileFriendly.class);
                case DOCUMENTS_MISSING_TITLE:
                    return new SiteProblemContent.DocumentsMissingTitle();
                case DOCUMENTS_MISSING_DESCRIPTION:
                    return new SiteProblemContent.DocumentsMissingDescription();
                case SLOW_AVG_RESPONSE:
                    if (content == null) {
                        log.error("Null content in DB for problem {}", problemType);
                        return new SiteProblemContent.SlowResponse(1000);
                    } else {
                        return OM.readValue(content, SiteProblemContent.SlowResponse.class);
                    }
                case SLOW_AVG_RESPONSE_WITH_EXAMPLES:
                    if (content == null) {
                        log.error("Null content in DB for problem {}", problemType);
                        return new SiteProblemContent.SlowResponseWithExamples(3000, List.of());
                    } else {
                        return OM.readValue(content, SiteProblemContent.SlowResponseWithExamples.class);
                    }
                case THREATS:
                    return new SiteProblemContent.Threats();
                case NO_METRIKA_COUNTER:
                    return new SiteProblemContent.NoMetrikaCounter();
                case TURBO_ERROR:
                    if (content == null) {
                        return new SiteProblemContent.TurboError(Collections.emptyList());
                    } else {
                        return OM.readValue(content, SiteProblemContent.TurboError.class);
                    }
                case TURBO_WARNING:
                    if (content == null) {
                        return new SiteProblemContent.TurboWarning(Collections.emptyList());
                    } else {
                        return OM.readValue(content, SiteProblemContent.TurboWarning.class);
                    }
                case YABROWSER_BADAD:
                    return OM.readValue(content, SiteProblemContent.YaBrowserBadAd.class);
                case TURBO_FEED_BAN:
                    return OM.readValue(content, SiteProblemContent.TurboFeedBan.class);
                case TURBO_DOCUMENT_BAN:
                    return OM.readValue(content, SiteProblemContent.TurboDocumentBan.class);
                case TURBO_HOST_BAN:
                    return OM.readValue(content, SiteProblemContent.TurboHostBan.class);
                case TURBO_HOST_BAN_INFO:
                    return OM.readValue(content, SiteProblemContent.TurboHostBanInfo.class);
                case TURBO_HOST_BAN_OK:
                    return OM.readValue(content, SiteProblemContent.TurboHostBanOk.class);
                case HOST_COMPANY_PROFILE_NOT_FILLED:
                    return OM.readValue(content, SiteProblemContent.HostCompanyProfileNotFilled.class);
                case TURBO_INSUFFICIENT_CLICKS_SHARE:
                    return OM.readValue(content, SiteProblemContent.TurboInsufficientClicksShare.class);
                case NO_CHATS:
                    return new SiteProblemContent.NoChats();
                case TOO_MANY_DOMAINS_ON_SEARCH:
                    return OM.readValue(content, SiteProblemContent.TooManyDomainsOnSearch.class);
                case SSL_CERTIFICATE_ERROR:
                    return OM.readValue(content, SiteProblemContent.SslCertificateError.class);
                case MAIN_MIRROR_IS_NOT_HTTPS:
                    return content == null ? null : OM.readValue(content, SiteProblemContent.MainMirrorIsNotHttps.class);
                case HOST_COMPANY_PROFILE_CREATED:
                    return content == null ? null : OM.readValue(content, SiteProblemContent.HostCompanyProfileCreated.class);
                case FAVICON_ERROR:
                    return new SiteProblemContent.FaviconError();
                case TURBO_LISTING_ERROR:
                    return new SiteProblemContent.TurboListingError();
                case NO_METRIKA_COUNTER_CRAWL_ENABLED:
                    return new SiteProblemContent.NoMetrikaCounterCrawlEnabled();
                case TURBO_URL_ERRORS:
                    return OM.readValue(content, SiteProblemContent.TurboUrlErrors.class);
                case NO_METRIKA_COUNTER_BINDING:
                    return new SiteProblemContent.NoMetrikaCounterBinding();
                case TURBO_INVALID_CART_URL:
                    return OM.readValue(content, SiteProblemContent.TurboInvalidCartUrl.class);
                case BIG_FAVICON_ABSENT:
                    return OM.readValue(content, SiteProblemContent.BigFaviconAbsent.class);
                case TURBO_RSS_ERROR:
                    return OM.readValue(content, SiteProblemContent.TurboRssError.class);
                case TURBO_YML_ERROR:
                    return OM.readValue(content, SiteProblemContent.TurboYmlError.class);
                case TURBO_RSS_WARNING:
                    return OM.readValue(content, SiteProblemContent.TurboRssWarning.class);
                case TURBO_YML_WARNING:
                    return OM.readValue(content, SiteProblemContent.TurboYmlWarning.class);
                case INSIGNIFICANT_CGI_PARAMETER:
                    return OM.readValue(content, SiteProblemContent.InsignificantCGIParameter.class);
                case DUPLICATE_CONTENT_ATTRS:
                    return OM.readValue(content, SiteProblemContent.DuplicateContentAttrs.class);
                case URL_ALERT_4XX:
                    return OM.readValue(content, SiteProblemContent.UrlAlert4xx.class);
                case URL_ALERT_5XX:
                    return OM.readValue(content, SiteProblemContent.UrlAlert5xx.class);
                case DISALLOWED_URLS_ALERT:
                    return OM.readValue(content, SiteProblemContent.DisallowedUrlsAlert.class);
                case VIDEOHOST_OFFER_FAILED:
                    return OM.readValue(content, SiteProblemContent.VideohostOfferFailed.class);
                case VIDEOHOST_OFFER_IS_NEEDED:
                    return OM.readValue(content, SiteProblemContent.VideohostOfferIsNeeded.class);
                case VIDEOHOST_OFFER_NEED_PAPER:
                    return OM.readValue(content, SiteProblemContent.VideohostOfferNeedPaper.class);
                default:
                    throw new IllegalArgumentException("Unknown problem type " + problemType);
            }
        } catch (IOException e) {
            throw new WebmasterException("Failed to deserialize problem of type " + problemType + " from string " + content,
                    new WebmasterErrorResponse.DataConsistencyErrorResponse(SiteProblemContentSerializer.class, ""), e);
        }
    }

    public static String serialize(SiteProblemContent problem) {
        try {
            if (problem == null) {
                return null;
            }
            if (problem instanceof SiteProblemContent.AbstractNoDataProblem) {
                return null;
            } else if (problem instanceof SiteProblemContent.MordaError) {
                SiteProblemContent.MordaError mordaError = (SiteProblemContent.MordaError) problem;
                return OM.writeValueAsString(new MordaErrorSerializationAdapter(
                        mordaError.getLastAccess(),
                        mordaError.getExtendedStatus().value(),
                        mordaError.getHttpStatusCode(),
                        mordaError.isFollowedRedirect())
                );
            } else {
                return OM.writeValueAsString(problem);
            }
        } catch (JsonProcessingException e) {
            throw new WebmasterException("Failed to serialize problem " + problem,
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(SiteProblemContentSerializer.class, ""), e);
        }
    }

    public static class MordaErrorSerializationAdapter {
        public DateTime lastAccess;
        public Integer httpStatusCode;
        public Integer extendedStatus;
        public boolean followedRedirect;

        public MordaErrorSerializationAdapter(@JsonProperty("lastAccess") DateTime lastAccess,
                                              @JsonProperty("extendedStatus") Integer extendedStatus,
                                              @JsonProperty("httpStatusCode") Integer httpStatusCode,
                                              @JsonProperty("followedRedirect") boolean followedRedirect) {
            this.lastAccess = lastAccess;
            this.extendedStatus = extendedStatus;
            this.httpStatusCode = httpStatusCode;
            this.followedRedirect = followedRedirect;
        }
    }
}
