package com.atlassian.bitbucket.internal.mirroring.mirror.client;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamServerType;
import com.atlassian.bitbucket.util.DevModeUtils;
import com.atlassian.httpclient.api.HttpClient;
import com.atlassian.httpclient.api.HttpStatus;
import com.atlassian.httpclient.api.Response;
import com.atlassian.httpclient.api.ResponsePromise;
import com.google.common.base.Throwables;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import javax.net.ssl.SSLException;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@Component
public class DefaultUpstreamValidator implements UpstreamValidator {

    private static final Logger log = LoggerFactory.getLogger(DefaultUpstreamValidator.class);

    private final HttpClient httpClient;
    private final I18nService i18nService;

    public DefaultUpstreamValidator(HttpClient httpClient, I18nService i18nService) {
        this.httpClient = httpClient;
        this.i18nService = i18nService;
    }

    @Nonnull
    @Override
    public Optional<KeyedMessage> validate(@Nonnull UpstreamServerType type, @Nonnull String url) {
        if (type == UpstreamServerType.BITBUCKET_CLOUD) {
            return validateCloudUpstream();
        } else if (type == UpstreamServerType.BITBUCKET_SERVER) {
            return validateServerUpstream(url);
        } else {
            throw new IllegalArgumentException(String.format("Invalid type %s.", type));
        }
    }

    private Optional<KeyedMessage> validateCloudUpstream() {
        return Optional.empty();
    }

    private Optional<KeyedMessage> validateServerUpstream(@Nonnull String url) {
        try {
            URL parsedUrl = new URL(url);
            if (!(DevModeUtils.isEnabled() || isHttpsUrl(parsedUrl))) {
                return Optional.of(message("bitbucket.mirroring.upstream.validation.url.not.https"));
            }

            String statusUrl = StringUtils.stripEnd(url, "/") + "/status";
            ResponsePromise responsePromise = httpClient.newRequest(statusUrl).get();
            Response response = responsePromise.get(10, TimeUnit.SECONDS);
            if (response.hasEntity() && !response.hasReadEntity()) {
                //Ensure that all resources are freed.
                IOUtils.closeQuietly(response.getEntityStream());
            }
            if (response.getStatusCode() != HttpStatus.OK.code) {
                log.warn("HTTP request to primary failed with {}", response.getStatusCode());
                return Optional.of(message("bitbucket.mirroring.upstream.validation.bad.response"));
            } else {
                return Optional.empty();
            }
        } catch (MalformedURLException e) {
            return Optional.of(message("bitbucket.mirroring.upstream.validation.url.invalid"));
        } catch (ExecutionException e) {
            log.warn("HTTP request to primary failed.", e);

            @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
            Throwable rootCause = Throwables.getRootCause(e);
            if (rootCause instanceof UnknownHostException) {
                return Optional.of(message("bitbucket.mirroring.upstream.validation.unknown.host", rootCause.getMessage()));
            } else if (rootCause instanceof SocketException) {
                return Optional.of(message("bitbucket.mirroring.upstream.validation.bad.connection", rootCause.getMessage()));
            } else if (rootCause instanceof SSLException) {
                return Optional.of(message("bitbucket.mirroring.upstream.validation.bad.connection.ssl", rootCause.getMessage()));
            } else {
                return Optional.of(message("bitbucket.mirroring.upstream.validation.general"));
            }
        } catch (TimeoutException | InterruptedException e) {
            log.warn("HTTP request to primary failed with timeout.", e);
            return Optional.of(message("bitbucket.mirroring.upstream.validation.timeout"));
        }
    }

    private static boolean isHttpsUrl(URL url) {
        return url != null && "https".equalsIgnoreCase(url.getProtocol());
    }

    private KeyedMessage message(String key) {
        return i18nService.createKeyedMessage(key);
    }

    private KeyedMessage message(String key, Object...args) {
        return i18nService.createKeyedMessage(key, args);
    }
}
