package ru.yandex.wmconsole.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.wmconsole.data.AddUrlRequest;
import ru.yandex.wmconsole.error.ClientException;
import ru.yandex.wmconsole.error.ClientProblem;
import ru.yandex.wmconsole.error.WMCExtraTagNameEnum;
import ru.yandex.wmconsole.service.error.WMCUserProblem;
import ru.yandex.wmtools.common.Constants;
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.servantlet.AbstractServantlet;
import ru.yandex.wmtools.common.sita.DocumentFormatEnum;
import ru.yandex.wmtools.common.sita.RedirectConfig;
import ru.yandex.wmtools.common.sita.RedirectListener;
import ru.yandex.wmtools.common.sita.SitaIncompleteResponseException;
import ru.yandex.wmtools.common.sita.SitaRedirectCycleException;
import ru.yandex.wmtools.common.sita.SitaRedirectService;
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.util.URLUtil;
import ru.yandex.wmtools.common.util.http.YandexHttpStatus;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;

/**
 * Checks if url should be indexed and passes it to the robot if necessary.
 *
 * @author ailyin
 */
public class AddUrlService extends AbstractUrlService implements Constants {
    private static final Logger log = LoggerFactory.getLogger(AddUrlService.class);

    private SitaRedirectService sitaRedirectService;
    private AddUrlLogService addUrlLogService;
    private ConsistentMainMirrorService consistentMainMirrorService;

    @Override
    protected boolean isIndexed(URL url) throws UserException, InternalException {
        return super.isIndexed(url) || super.isIndexedTr(url);
    }

    public void addUrl(AddUrlRequest addUrlRequest, boolean checkMainMirror, String yaDomain,
            boolean ignoreRobotsTxt) throws UserException, InternalException
    {
        final String mainMirror = consistentMainMirrorService.getMainMirror(URLUtil.getHostName(addUrlRequest.getUrl(), false));
        if (!isMainMirror(addUrlRequest.getUrl(), mainMirror)) {//todo use canonical hostname
            String message = "Host is a mirror of " + mainMirror + "!";
            ExtraTagInfo tagInfo = new ExtraTagInfo(ExtraTagNameEnum.ALT, mainMirror);
            if (checkMainMirror) {
                try {
                    final URL mainMirrorUrl = replaceWithMainMirror(addUrlRequest.getUrl(), mainMirror);
                    log.debug("mainMirrorUrl=" + mainMirrorUrl);
                    WMCUserProblem error = isIndexed(mainMirrorUrl)
                            ? WMCUserProblem.HOST_IS_MIRROR_MAIN_MIRROR_IS_INDEXED
                            : WMCUserProblem.HOST_IS_MIRROR_MAIN_MIRROR_IS_NOT_INDEXED;
                    throw new UserException(error, message, tagInfo,
                            new ExtraTagInfo(WMCExtraTagNameEnum.MAIN_MIRROR, getDisplayedMainMirrorUrl(mainMirrorUrl)));
                } catch (MalformedURLException e1) {
                    throw new AssertionError("should never happen! url " + mainMirror + " is invalid.");
                }
            }
            throw new UserException(WMCUserProblem.HOST_IS_MIRROR, message, tagInfo);
        }

        markAndCheckRequestIp(addUrlRequest);
        markAndCheckRequestUrl(addUrlRequest);

        if (isIndexed(addUrlRequest.getUrl())) {
            throw new UserException(WMCUserProblem.URL_IS_ALREADY_INDEXED,
                    "Url " + addUrlRequest.getUrl() + " was already indexed.");
        }

        URL url = addUrlRequest.getUrl();
        SitaUrlFetchRequest urlFetchRequest = new SitaUrlFetchRequestBuilder(url)
                .setDocumentFormat(DocumentFormatEnum.DF_HTTP_RESPONSE)
                .setUserAgent(UserAgentEnum.ROBOT)
                .createSitaUrlFetchRequest();

        SitaUrlFetchResponse urlFetchResponse;
        try {
            urlFetchResponse = sitaRedirectService.followRedirects(urlFetchRequest,
                    RedirectConfig.DEFAULT_REDIRECT_CONFIG, getRedirectListener(addUrlRequest));
        } catch (SitaRedirectCycleException e) {
            throw new ClientException(ClientProblem.CYCLIC_REDIRECT, "Cyclic redirect to " + e.getRedirectUri());
        } catch (SitaIncompleteResponseException e) {
            throw new ClientException(ClientProblem.HOST_NOT_AVAILABLE, "Host is not available or request timeout exceeded");
        }

        if (ignoreRobotsTxt || urlFetchResponse.isAllowedInRobotsTxt() == null) {
            log.warn("Unknown robots.txt state for {}", url);
        } else {
            if (urlFetchResponse.isAllowedInRobotsTxt()) {
                log.debug("{} is allowed in robots.txt.", url);
                YandexHttpStatus sitaHttpStatus = urlFetchResponse.getSitaHttpStatus();
                if (sitaHttpStatus != YandexHttpStatus.HTTP_200_OK) {
                    log.info("Server http status: {}", sitaHttpStatus);
                    if (sitaHttpStatus.getCode() >= 100 && sitaHttpStatus.getCode() < 600) {
                        throw new ClientException(ClientProblem.URL_RETURNED_WRONG_CODE,
                                "Url " + url + " returned wrong code: " + sitaHttpStatus.getCode() + ".",
                                new ExtraTagInfo(WMCExtraTagNameEnum.HTTP_CODE, String.valueOf(sitaHttpStatus.getCode())));

                    } else {
                        throw new UserException(WMCUserProblem.SERVER_IS_NOT_AVAILABLE, "Url " + url + " is not available.");
                    }
                }
            } else {
                throw new UserException(WMCUserProblem.URL_IS_DISALLOWED_IN_ROBOTS_TXT,
                        "Url " + url + " is disallowed in robots.txt.");
            }
        }

        addUrlLogService.logUrl(addUrlRequest, yaDomain);
    }

    private RedirectListener getRedirectListener(final AddUrlRequest addUrlRequest) {
        return new RedirectListener() {
            @Override
            public void beforeRedirect(URI from, URI to) throws UserException {
                checkRedirectUrl(to, addUrlRequest);
            }

            @Override
            public void lastTarget(URI uri) throws UserException {
                addUrlRequest.setResultingUri(uri);
                markAndCheckRedirectUrl(uri, addUrlRequest);
            }
        };
    }

    protected void markAndCheckRequestIp(AddUrlRequest addUrlRequest) throws UserException {
    }

    protected void markAndCheckRequestUrl(AddUrlRequest addUrlRequest) throws UserException {
    }

    protected void markAndCheckRedirectUrl(URI uri, AddUrlRequest addUrlRequest) throws UserException {
    }

    protected void checkRedirectUrl(URI uri, AddUrlRequest addUrlRequest) throws UserException {
    }

    /**
     * Замещает хост и схему в url на хост и схему главного зеркала.
     * NOTE: для большинства хостов getMainMirror возвращает имя хоста, но для хостов с https возвращается https://hostname
     * см. WMCON-3680
     *
     * @param url           url, в котором осуществляется замена
     * @param mainMirrorUrl url главного зеркала [scheme://]hostname
     * @return              url, в котором имя хоста и схема заменены на имя хоста и схему для зеркала
     */
    private URL replaceWithMainMirror(final URL url, final String mainMirrorUrl) throws UserException, MalformedURLException {
            final URL mm = AbstractServantlet.prepareUrl(mainMirrorUrl, true);
            // Протокол берем от главного зеркала
            final String protocol = mm.getProtocol();
            // Хост всегда берем от главного зеркала
            final String host = mm.getHost();
            // Остальное от исходного url
            final int port = url.getPort();
            final String file = url.getFile();
            return new URL(protocol, host, port, file);
    }

    /**
     * Получение строкового url с замененным главным зеркалом для пользователя (без схемы кроме https)
     * @param mainMirrorUrl url с замененным главным зеркалом
     * @return строка для отображения пользователю
     */
    private String getDisplayedMainMirrorUrl(URL mainMirrorUrl) {
        final String protocol = mainMirrorUrl.getProtocol();
        final int prefixLength = protocol.length() + SCHEME_DELIMITER.length();
        final String mainMirror = mainMirrorUrl.toString();
        return "https".equals(protocol) ? mainMirror : mainMirror.substring(prefixLength);
    }

    private boolean isMainMirror(URL url, String mainMirror) throws UserException {
        final URL mm = AbstractServantlet.prepareUrl(mainMirror, true);
        return url.getHost().equalsIgnoreCase(mm.getHost()) && url.getProtocol().equals(mm.getProtocol());
    }

    @Required
    public void setSitaRedirectService(SitaRedirectService sitaRedirectService) {
        this.sitaRedirectService = sitaRedirectService;
    }

    @Required
    public void setAddUrlLogService(AddUrlLogService addUrlLogService) {
        this.addUrlLogService = addUrlLogService;
    }

    @Required
    public void setConsistentMainMirrorService(ConsistentMainMirrorService consistentMainMirrorService) {
        this.consistentMainMirrorService = consistentMainMirrorService;
    }
}
