package ru.yandex.webmaster3.core.zora;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedList;

import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.client.utils.URIUtils;

import ru.yandex.webmaster3.core.zora.data.response.ZoraUrlFetchResponse;
import ru.yandex.webmaster3.core.zora.go_data.request.GoZoraRequest;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.sita.RedirectConfig;
import ru.yandex.wmtools.common.sita.RedirectListener;
import ru.yandex.wmtools.common.sita.SitaException;
import ru.yandex.wmtools.common.sita.SitaRedirectCycleException;
import ru.yandex.wmtools.common.sita.SitaRedirectInfo;
import ru.yandex.wmtools.common.util.http.YandexHttpStatus;

/**
 * @author aherman
 */
@Slf4j
public class ZoraRedirectService {

    @Setter
    private GoZoraService goZoraService;

    public ZoraUrlFetchResponse followRedirectsExtended(GoZoraRequest request) throws InternalException, UserException  {
        return followRedirectsExtended(request, RedirectConfig.DEFAULT_REDIRECT_CONFIG);
    }

    public ZoraUrlFetchResponse followRedirectsExtended(GoZoraRequest request,
                                                        RedirectConfig redirectConfig)
            throws InternalException, UserException {
        return followRedirectsExtended(request, redirectConfig, RedirectListener.DEFAULT_LISTENER);
    }

    public ZoraUrlFetchResponse followRedirectsExtended(@NonNull GoZoraRequest request,
                                                        RedirectConfig redirectConfig,
                                                        RedirectListener redirectListener)
            throws InternalException, UserException {
        ZoraUrlFetchResponse response = null;
        LinkedList<URI> redirects = new LinkedList<>();
        URI originalUri = request.getUri();
        redirects.add(originalUri);
        for (int i = 0; i < redirectConfig.getMaxRedirects(); i++) {

            response = ZoraConversionUtil.toUrlFetchResponse(goZoraService.executeRequestFetchResponse(request));

            if (!isRedirect(response)) {
                break;
            }
            URI newUri = getRedirectUrl(originalUri, response, redirectConfig);
            if (redirects.contains(newUri)) {
                throw new SitaRedirectCycleException("Circular redirect", newUri);
            }
            redirects.add(newUri);
            log.debug("Follow redirect: {} -> {}", request.getUrl(), newUri);
            redirectListener.beforeRedirect(request.getUri(), newUri);
            request = request.withUrl(newUri.toString());
        }
        redirectListener.lastTarget(request.getUri());
        if (response != null) {
            response.setSitaRedirectInfo(new SitaRedirectInfo(redirects));
        }
        return response;
    }

    URI getRedirectUrl(URI originalUri, ZoraUrlFetchResponse response, RedirectConfig redirectConfig) {
        if (!StringUtils.isEmpty(response.getRedirTarget())) {
            try {
                log.debug("Redirect from redirTarget: {}", response.getRedirTarget());
                return new URI(response.getRedirTarget());
            } catch (URISyntaxException e) {
                throw new SitaException("Unable to parse redirect location", e);
            }
        }
        if (!response.hasDocument()) {
            throw new SitaException("Response does not have http content");
        }
        Header location = response.getHttpResponse().getFirstHeader("location");
        if (location == null || StringUtils.isEmpty(location.getValue())) {
            throw new SitaException("Response does not have Location header");
        }

        // fix: WMCON-5098
        if (StringUtils.isEmpty(originalUri.getPath())) {
            originalUri = originalUri.resolve("/");
        }

        URI newUri;
        try {
            newUri = new URI(location.getValue()).normalize();
            newUri = URIUtils.rewriteURI(newUri);
            if (!newUri.isAbsolute()) {
                if (redirectConfig.isRequireAbsoluteLocation()) {
                    throw new SitaException("Redirect location should be absolute URI: " + newUri);
                }
                newUri = URIUtils.resolve(originalUri, newUri);
            }
        } catch (URISyntaxException e) {
            throw new SitaException("Unable to parse redirect location", e);
        }

        if (redirectConfig.isRequireSameOrigin()) {
            if (!originalUri.getScheme().equals(newUri.getScheme())) {
                throw new SitaException(
                        "Redirect should have same origin, but schema mismatch: from=" + originalUri + ", to=" + newUri);
            }
            if (!originalUri.getHost().equals(newUri.getHost())) {
                throw new SitaException(
                        "Redirect should have same origin, but host mismatch: from=" + originalUri + ", to=" + newUri);
            }
            if (originalUri.getPort() != newUri.getPort()) {
                throw new SitaException(
                        "Redirect should have same origin, but port mismatch: from=" + originalUri + ", to=" + newUri);
            }
        }
        return newUri;
    }

    private boolean isRedirect(ZoraUrlFetchResponse urlFetchResponse) {
        YandexHttpStatus sitaHttpStatus = urlFetchResponse.getExtendedHttpStatus();
        switch (sitaHttpStatus) {
            case HTTP_301_MOVED_PERMANENTLY:
            case HTTP_302_FOUND:
            case HTTP_303_SEE_OTHER:
            case HTTP_307_TEMPORARY_REDIRECT:
                return true;

            default:
                return false;
        }
    }
}
