package ru.yandex.webmaster3.core.zora.data.response;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import lombok.With;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.entity.ContentType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.sita.SitaException;
import ru.yandex.wmtools.common.sita.SitaRedirectInfo;
import ru.yandex.wmtools.common.sita.YandexCharset;
import ru.yandex.wmtools.common.sita.YandexDocumentLanguage;
import ru.yandex.webmaster3.core.util.http.YandexMimeType;
import ru.yandex.wmtools.common.util.http.HttpUtils;
import ru.yandex.wmtools.common.util.http.YandexHttpStatus;

/**
 * @author aherman
 */
@ToString
@Builder
@With
public class ZoraUrlFetchResponse {
    private static final Logger log = LoggerFactory.getLogger(ZoraUrlFetchResponse.class);

    private final Boolean isAllowedInRobotsTxt;
    private final String ipAddress;
    private final String redirTarget;

    private final String robotsTxtContent;
    private final String originalHttpHeader;
    private final HttpResponse httpResponse;
    private final Integer serverHttpStatusCode;
    private final YandexHttpStatus extendedHttpStatus;
    private final Long responseTime;
//    TODO: раньше на эти параметры было повешенно NonNull, но ZoraConversionUtil:89 присваивался null ¯\_(ツ)_/¯ . Может повесить @Nullable или оставить как было?
    private final YandexCharset charset;
    private final YandexDocumentLanguage language;
    private final YandexMimeType mimeType;
    @Nullable
    @Getter
    private final ZoraSslCertErrorEnum sslErrorExplanation;
    @Getter(onMethod_ = @JsonIgnore)
    private final Charset charsetJava;


    private SitaRedirectInfo sitaRedirectInfo = SitaRedirectInfo.NO_REDIRECTS;

    private String responseJson;

    public Boolean isAllowedInRobotsTxt() {
        return isAllowedInRobotsTxt;
    }

    public String getIpAddress() {
        return ipAddress;
    }

    public String getRedirTarget() {
        return redirTarget;
    }

    public boolean hasDocument() {
        return httpResponse != null;
    }

    public HttpResponse getHttpResponse() {
        if (!hasDocument()) {
            throw new SitaException("Unable to read http document headers");
        }
        return httpResponse;
    }

    public HttpResponse getDocument() {
        if (!hasDocument()) {
            throw new SitaException("Unable to read http document");
        }
        try {
            return httpResponse;
        } catch (Exception e) {
            throw new SitaException("Unable to read http document", e);
        }
    }

    public String getRobotsTxtContent() {
        return robotsTxtContent;
    }

    public String getHttpHeader() {
        if (originalHttpHeader == null) {
            return null;
        }
        return HttpUtils.cleanHttpHeader(originalHttpHeader);
    }

    public Integer getServerHttpStatusCode() {
        return serverHttpStatusCode;
    }

    public YandexHttpStatus getExtendedHttpStatus() {
        return extendedHttpStatus;
    }

    public Long getResponseTime() {
        return responseTime;
    }

    @NotNull
    public YandexCharset getCharset() {
        return charset;
    }

    @NotNull
    public YandexDocumentLanguage getLanguage() {
        return language;
    }

    @NotNull
    public YandexMimeType getMimeType() {
        return mimeType;
    }

    @Nullable
    public Reader getDocumentContent() throws InternalException {
        if (httpResponse == null) {
            return null;
        }
        boolean gzipEncoded = HttpUtils.isGzipEncoded(getDocument());
        return HttpUtils.getResponseContent(getDocument(), gzipEncoded, guessCharsetOrUTF8(getDocument()));
    }

    @Nullable
    public Reader getDocumentContent(Charset charset) throws InternalException {
        if (httpResponse == null) {
            return null;
        }
        boolean gzipEncoded = HttpUtils.isGzipEncoded(getDocument());
        return HttpUtils.getResponseContent(getDocument(), gzipEncoded, charset);
    }

    @Nullable
    public InputStream getDocumentContentStream() throws InternalException {
        if (httpResponse == null) {
            return null;
        }
        boolean gzipEncoded = HttpUtils.isGzipEncoded(getDocument());
        return HttpUtils.getResponseContentStream(getDocument(), gzipEncoded);
    }

    @Nullable
    public String getDocumentContentAsString() throws IOException, InternalException {
        Reader reader = getDocumentContent();
        if (reader == null) {
            return null;
        }
        return IOUtils.toString(reader);
    }

    @Nullable
    public String getDocumentContentAsString(Charset charset) throws IOException, InternalException {
        Reader reader = getDocumentContent(charset);
        if (reader == null) {
            return null;
        }
        return IOUtils.toString(reader);
    }

    public void setSitaRedirectInfo(SitaRedirectInfo sitaRedirectInfo) {
        this.sitaRedirectInfo = sitaRedirectInfo;
    }

    public SitaRedirectInfo getSitaRedirectInfo() {
        return sitaRedirectInfo;
    }

    public String getResponseJson() {
        return responseJson;
    }

    public void setResponseJson(String responseJson) {
        this.responseJson = responseJson;
    }

    private Charset guessCharsetOrUTF8(HttpResponse httpResponse) {
        Charset defaultCharset = StandardCharsets.UTF_8;
        try {
            return Optional.ofNullable(httpResponse.getEntity())
                    .map(ContentType::getOrDefault)
                    .map(ContentType::getCharset)
                    .orElse(defaultCharset);

        } catch (UnsupportedCharsetException ex) {
            log.info("Content type contains invalid charset!");
        }
        return defaultCharset;
    }
}
