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

import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.httpclient.api.HttpClient;
import com.atlassian.httpclient.api.Request;
import com.atlassian.httpclient.api.ResponsePromise;
import com.atlassian.httpclient.api.ResponseTransformation;
import com.atlassian.httpclient.api.factory.HttpClientFactory;
import com.atlassian.httpclient.api.factory.HttpClientOptions;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import static com.atlassian.bitbucket.internal.mirroring.mirror.MirrorConstants.*;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * Creates a new {@link HttpClient} for use by the mirror when contacting the upstream server
 */
@Component("mirroringHttpClientFactory")
public class MirroringHttpClientFactory {
    private static final Logger log = getLogger(MirroringHttpClientFactory.class);

    private final HttpClientFactory factory;
    private final ApplicationPropertiesService propertiesService;

    @Autowired
    public MirroringHttpClientFactory(HttpClientFactory factory, ApplicationPropertiesService propertiesService) {
        this.factory = factory;
        this.propertiesService = propertiesService;
    }

    @Nonnull
    public CloseableHttpClient create() {
        HttpClientOptions options = new HttpClientOptions();
        options.setSocketTimeout(propertiesService.getPluginProperty(
                PROPERTY_UPSTREAM_REQUEST_SO_TIMEOUT, DEFAULT_UPSTREAM_REQUEST_SO_TIMEOUT_SECONDS), TimeUnit.SECONDS);
        options.setRequestTimeout(propertiesService.getPluginProperty(
                PROPERTY_UPSTREAM_REQUEST_TIMEOUT, DEFAULT_UPSTREAM_REQUEST_TIMEOUT_SECONDS), TimeUnit.SECONDS);

        HttpClient delegate = factory.create(options);
        return new CloseableHttpClient() {
            @Override
            public void close() throws IOException {
                try {
                    factory.dispose(delegate);
                } catch (Exception e) {
                    // this may happen if we're shutting down and the http-client bundle
                    // has already been uninstalled so we should only warn
                    log.warn("Failed to dispose a HttpClient");
                    log.debug("Dispose error:", e);
                }
            }

            @Override
            public Request.Builder newRequest() {
                return delegate.newRequest();
            }

            @Override
            public Request.Builder newRequest(URI uri) {
                return delegate.newRequest(uri);
            }

            @Override
            public Request.Builder newRequest(String uri) {
                return delegate.newRequest(uri);
            }

            @Override
            public Request.Builder newRequest(URI uri, String contentType, String entity) {
                return delegate.newRequest(uri, contentType, entity);
            }

            @Override
            public Request.Builder newRequest(String uri, String contentType, String entity) {
                return delegate.newRequest(uri, contentType, entity);
            }

            @Override
            public void flushCacheByUriPattern(Pattern uriPattern) {
                delegate.flushCacheByUriPattern(uriPattern);
            }

            @Override
            public <A> ResponseTransformation.Builder<A> transformation() {
                return delegate.transformation();
            }

            @Override
            public ResponsePromise execute(Request request) {
                return delegate.execute(request);
            }
        };
    }

    public interface CloseableHttpClient extends HttpClient, Closeable {

    }
}
