package ru.yandex.qe.bus.factories.client;

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLSession;

import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.endpoint.ConduitSelector;
import org.apache.cxf.jaxrs.client.ClientConfiguration;
import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.ConnectionType;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.http.conn.ssl.StrictHostnameVerifier;

import ru.yandex.qe.bus.factories.BusEndpointBuilder;
import ru.yandex.qe.bus.features.version.VersionFeature;
import ru.yandex.qe.http.certificates.TrustManagersProvider;

import static ru.yandex.qe.bus.features.version.VersionFeature.configureVersion;
import static ru.yandex.qe.bus.features.version.VersionFeature.findInterfaceVersionForClass;

/**
 * Established by terry on 30.01.14.
 */
public class BusClientBuilder extends BusEndpointBuilder {
    private Class<?> serviceClass;
    private boolean inheritHeaders;
    private long connectTimeout;
    private long receiveTimeout;
    private ConduitSelector conduitSelector;
    private boolean allowChunking = true;
    private boolean ignoreCertificates = false;
    private boolean autoRedirect = true;
    private ConnectionType connectionType = ConnectionType.KEEP_ALIVE;
    private boolean useAsyncHttpClient;
    private String keyStoreFile;
    private String keyStorePassword;

    private Map<String, Object> requestContext;

    //used via reflection


    public void setKeyStoreFile(final String keyStoreFile) {
        this.keyStoreFile = keyStoreFile;
    }

    public void setKeyStorePassword(final String keyStorePassword) {
        this.keyStorePassword = keyStorePassword;
    }

    public void setUseAsyncHttpClient(final boolean useAsyncHttpClient) {
        this.useAsyncHttpClient = useAsyncHttpClient;
    }

    public void setAutoRedirect(final boolean autoRedirect) {
        this.autoRedirect = autoRedirect;
    }

    public void setRequestContext(Map<String, Object> requestContext) {
        this.requestContext = requestContext;
    }

    public void setConduitSelector(ConduitSelector conduitSelector) {
        this.conduitSelector = conduitSelector;
    }

    public void setServiceClass(@Nonnull Class<?> serviceClass) {
        this.serviceClass = serviceClass;
    }

    public void setInheritHeaders(boolean inheritHeaders) {
        this.inheritHeaders = inheritHeaders;
    }

    public void setConnectTimeout(long connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public void setReceiveTimeout(long receiveTimeout) {
        this.receiveTimeout = receiveTimeout;
    }

    public void setAllowChunking(boolean allowChunking) {
        this.allowChunking = allowChunking;
    }

    public void setKeepAlive(boolean isKeepAlive) {
        this.connectionType = isKeepAlive ? ConnectionType.KEEP_ALIVE : ConnectionType.CLOSE;
    }

    public void setIgnoreCertificates(boolean ignoreCertificates) {
        this.ignoreCertificates = ignoreCertificates;
    }

    public Class<?> getServiceClass() {
        return serviceClass;
    }

    public Object build() {
        return build(address);
    }

    public Object build(String address) {
        final JAXRSClientFactoryBean clientFactoryBean = new JAXRSClientFactoryBean();
        prepareFactoryBean(clientFactoryBean, address);
        clientFactoryBean.setServiceClass(serviceClass);
        clientFactoryBean.setInheritHeaders(inheritHeaders);
        clientFactoryBean.setConduitSelector(conduitSelector);

        final VersionFeature.PackageInfo interfaceInfo = findInterfaceVersionForClass(serviceClass);
        configureVersion(clientFactoryBean, null, null, interfaceInfo.getTitle(), interfaceInfo.getVersion());

        final Object proxy = clientFactoryBean.create(serviceClass);
        final ClientConfiguration config = WebClient.getConfig(proxy);

        final HTTPConduit httpConduit = config.getHttpConduit();
        if (httpConduit != null) {
            TLSClientParameters tlsClientParameters = new TLSClientParameters();
//            tlsClientParameters.setUseHttpsURLConnectionDefaultSslSocketFactory(true);
            if (ignoreCertificates) {
                tlsClientParameters.setTrustManagers(TrustManagersProvider.getInstance().trustAllManagers());
                tlsClientParameters.setDisableCNCheck(true);
                tlsClientParameters.setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String host, SSLSession sslSession) {
                        return true;
                    }
                });
            } else {
                tlsClientParameters.setTrustManagers(TrustManagersProvider.getInstance().getTrustManagers());
                tlsClientParameters.setHostnameVerifier(useAsyncHttpClient ? new StrictHostnameVerifier() : HttpsURLConnection.getDefaultHostnameVerifier());
            }
            if (keyStoreFile != null &&! keyStoreFile.trim().isEmpty()) {
                try {
                    KeyStore keyStore = KeyStore.getInstance("PKCS12");
                    try (final InputStream inputStream = new FileInputStream(keyStoreFile)) {
                        keyStore.load(inputStream, keyStorePassword != null ? keyStorePassword.toCharArray() : null);
                        KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
                                KeyManagerFactory.getDefaultAlgorithm());
                        kmfactory.init(keyStore, keyStorePassword != null ? keyStorePassword.toCharArray() : null);
                        tlsClientParameters.setKeyManagers(kmfactory.getKeyManagers());
                    }
                } catch (Exception e) {
                    throw new RuntimeException("Internal error", e);
                }
            }

            httpConduit.setTlsClientParameters(tlsClientParameters);
            HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
            httpClientPolicy.setConnectionTimeout(connectTimeout);
            httpClientPolicy.setReceiveTimeout(receiveTimeout);
            httpClientPolicy.setAutoRedirect(autoRedirect); // some services may redirect you (possible temporally)
            httpClientPolicy.setAllowChunking(allowChunking);
            httpClientPolicy.setConnection(connectionType);
            httpConduit.setClient(httpClientPolicy);

        }

        if (requestContext != null) {
            config.getRequestContext().putAll(requestContext);
        }
        return proxy;
    }

}
