package ru.yandex.wmconsole.servantlet;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.common.framework.core.ServRequest;
import ru.yandex.common.framework.core.ServResponse;
import ru.yandex.misc.ip.Ipv4Address;
import ru.yandex.wmconsole.data.ServerResponseInfo;
import ru.yandex.wmconsole.data.wrappers.ServerResponseInfoWrapper;
import ru.yandex.wmconsole.data.wrappers.StringWrapper;
import ru.yandex.wmtools.common.error.ExtraTagInfo;
import ru.yandex.wmtools.common.error.ExtraTagNameEnum;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.error.UserProblem;
import ru.yandex.wmtools.common.sita.DocumentFormatEnum;
import ru.yandex.wmtools.common.sita.SitaRequestTimeout;
import ru.yandex.wmtools.common.sita.SitaService;
import ru.yandex.wmtools.common.sita.SitaUrlFetchRequest;
import ru.yandex.wmtools.common.sita.SitaUrlFetchRequestBuilder;
import ru.yandex.wmtools.common.sita.SitaUrlFetchResponse;
import ru.yandex.wmtools.common.sita.UserAgentEnum;
import ru.yandex.wmtools.common.sita.YandexCharset;
import ru.yandex.wmtools.common.sita.YandexMimeType;
import ru.yandex.wmtools.common.util.http.YandexHttpStatus;

/**
 * Support version does not check for host owning
 * @author amima
 * @author azakharov
 */
public class SupportHttpResponseServantlet extends WMCAuthenticationServantlet {
    private static final Logger log = LoggerFactory.getLogger(SupportHttpResponseServantlet.class);

    private static final String PARAM_URL = "url";
    private static final String PARAM_USERAGENT = "useragent";
    private static final String PARAM_IF_MODIFIED_SINCE_ON = "modified";
    private static final String PARAM_IF_MODIFIED_SINCE_DATE = "date";

    private static final String LOCATION_HEADER = "Location";

    private SitaService sitaService;

    @Override
    protected void doProcess(ServRequest req, ServResponse res, long userId) throws UserException, InternalException {
        String urlString = getRequiredStringParam(req, PARAM_URL);
        URL url = prepareUrl(urlString, true);

        UserAgentEnum userAgent = UserAgentEnum.R.valueOfOrNull(req.getParam(PARAM_USERAGENT));
        if (userAgent == null) {
            userAgent = UserAgentEnum.ROBOT;
        }

        Boolean ifModifiedSinceOn = req.getCheckBoxParamAsBoolean(PARAM_IF_MODIFIED_SINCE_ON, Boolean.FALSE);
        Date ifModifiedSince = null;
        String ifModifiedSinceDateStr = req.getParam(PARAM_IF_MODIFIED_SINCE_DATE);
        if (ifModifiedSinceOn &&  ifModifiedSinceDateStr!= null
                && ifModifiedSinceDateStr.length() > 0) {
            try {
                DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
                dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
                ifModifiedSince = dateFormat.parse(req.getParam(PARAM_IF_MODIFIED_SINCE_DATE));
            } catch (ParseException e) {
                throw new UserException(UserProblem.ILLEGAL_PARAM_VALUE,
                        "Unable to parse date " + ifModifiedSinceDateStr,
                        new ExtraTagInfo(ExtraTagNameEnum.PARAM, PARAM_IF_MODIFIED_SINCE_DATE),
                        new ExtraTagInfo(ExtraTagNameEnum.VALUE, ifModifiedSinceDateStr));
            }
        }

        ServerResponseInfo info;
        log.debug("use sita");
        info = getHttpResponseWithSita(url, userAgent, ifModifiedSinceOn, ifModifiedSince, true);

        res.addData(new StringWrapper(info.getSitaRequestJson(), "sita-request"));
        res.addData(new StringWrapper(info.getSitaResponseJson(), "sita-response"));
        res.addData(new ServerResponseInfoWrapper(info));
    }

    public ServerResponseInfo getHttpResponseWithSita(
            @NotNull URL url,
            @NotNull UserAgentEnum userAgent,
            boolean ifModifiedSinceOn,
            @Nullable Date ifModifiedSinceDate,
            boolean retainSitaRequestResponse) throws UserException, InternalException
    {
        SitaUrlFetchRequestBuilder builder = new SitaUrlFetchRequestBuilder(url)
                .setUserAgent(userAgent)
                .setDocumentFormat(DocumentFormatEnum.DF_HTTP_RESPONSE)
                .setRequestTimeout(SitaRequestTimeout._15_SECONDS);

        if (ifModifiedSinceOn) {
            Date d = ifModifiedSinceDate != null ? ifModifiedSinceDate : new Date();
            log.debug("d.getTime() = " + d.getTime());
            builder.setIfModifiedSince(Long.valueOf(d.getTime() / 1000L).intValue());
        }

        SitaUrlFetchRequest urlFetchRequest = builder.createSitaUrlFetchRequest();

        SitaUrlFetchResponse urlFetchResponse = sitaService.requestExtended(urlFetchRequest, true);
        YandexHttpStatus status = urlFetchResponse.getSitaHttpStatus();
        String ip = urlFetchResponse.getIp4() != null ?
                Ipv4Address.valueOf(urlFetchResponse.getIp4().intValue()).toString() : null;
        String statusName = YandexHttpStatus.getStatusString(status);
        Map<String, Collection<String>> headers = getHeaders(urlFetchResponse);
        String location =
                headers.get(LOCATION_HEADER) != null ? headers.get(LOCATION_HEADER).iterator().next() : null;

        ServerResponseInfo serverResponseInfo = new ServerResponseInfo(
                status.getCode(),
                statusName,
                headers,
                ip,
                urlFetchResponse.getResponseTime(),
                location,
                null
        );
        if (retainSitaRequestResponse) {
            serverResponseInfo.setSitaRequestJson(urlFetchRequest.getRequestJson());
            serverResponseInfo.setSitaResponseJson(urlFetchResponse.getResponseJson());
        }
        try {
            appendTextBody(serverResponseInfo, urlFetchResponse);
        } catch (InternalException e) {
            log.error("Exception reading http content", e);
        } catch (IOException e) {
            log.error("Exception reading http content", e);
        }

        return serverResponseInfo;
    }

    private static Map<String, Collection<String>> getHeaders(SitaUrlFetchResponse responseInfo) {
        if (!responseInfo.hasDocument()) {
            return Collections.emptyMap();
        }
        Map<String, Collection<String>> headers = new LinkedHashMap<>();
        for (Header header : responseInfo.getParsedHttpHeaders().getAllHeaders()) {
            Collection<String> hs = headers.get(header.getName());
            if (hs == null) {
                hs = new LinkedList<>();
                headers.put(header.getName(), hs);
            }
            hs.add(header.getValue());
        }
        return headers;
    }


    private void appendTextBody(ServerResponseInfo info, SitaUrlFetchResponse sitaUrlFetchResponse)
            throws InternalException, IOException
    {
        InputStream documentContentStream = sitaUrlFetchResponse.getDocumentContentStream();
        if (documentContentStream == null) {
            return;
        }

        String body = "";

        // determine enconding

        Boolean isText = sitaUrlFetchResponse.getMimeType() == YandexMimeType.MIME_TEXT
                || sitaUrlFetchResponse.getMimeType() == YandexMimeType.MIME_HTML
                || sitaUrlFetchResponse.getMimeType() == YandexMimeType.MIME_XML;

        info.setText(isText);
        if (isText) {
            YandexCharset yandexCharset = sitaUrlFetchResponse.getCharset();

            Charset charset = SitaService.UTF_8;
            if (yandexCharset.getCharsetName() != null) {
                try {
                    charset = Charset.forName(yandexCharset.getCharsetName());
                } catch (UnsupportedCharsetException | IllegalCharsetNameException e) {
                    log.warn("Unknown charset, fallback to UTF-8: " + yandexCharset);
                }
            }

            body = sitaUrlFetchResponse.getDocumentContentAsString(charset);
            body = StringUtils.trimToEmpty(body);
            info.setEncoding(getCharsetDisplayName(charset));
        }

        long size = (long) body.length();

        double smallSize = (double) size / (double) 1024;
        info.setSize(smallSize);
        info.setBody(body);
    }

    private String getCharsetDisplayName(Charset charset) {
        StringBuilder sb = new StringBuilder(charset.displayName());
        Set<String> aliases = charset.aliases();
        if (!aliases.isEmpty()) {
            sb.append('(').append(StringUtils.join(aliases, ", ")).append(')');
        }
        return sb.toString();
    }

    @Required
    public void setNewSitaService(SitaService sitaService) {
        this.sitaService = sitaService;
    }
}
