package org.echocat.gradle.plugins.golang.vcs.git;

import org.eclipse.jgit.transport.http.HttpConnection;
import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Proxy;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Collection;

import static java.lang.System.getProperty;
import static java.nio.file.Files.isRegularFile;
import static java.nio.file.Files.newInputStream;
import static org.apache.commons.io.IOUtils.closeQuietly;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

public class HttpConnectionFactoryImpl extends JDKHttpConnectionFactory {


    @Override
    public HttpConnection create(URL url) throws IOException {
        return configure(super.create(url));
    }

    @Override
    public HttpConnection create(URL url, Proxy proxy) throws IOException {
        return configure(super.create(url, proxy));
    }

    @Nonnull
    protected HttpConnection configure(@Nonnull HttpConnection input) throws IOException {
        try {
            final KeyStore keyStore = loadKeyStore();

            final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            final TrustManager[] defaultTrustManagers = trustManagerFactory.getTrustManagers();

            final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, null);
            final KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

            input.configure(keyManagers, defaultTrustManagers, null);

            input.setInstanceFollowRedirects(true);

            return input;
        } catch (final GeneralSecurityException e) {
            throw new IOException(e.getMessage(), e);
        }
    }

    @Nonnull
    protected KeyStore loadKeyStore() throws GeneralSecurityException, IOException {
        final KeyStore result = loadDefaultKeyStore();

        final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

        try (final InputStream is = getClass().getResourceAsStream("/org/echocat/gradle/plugins/golang/ca.crt")) {
            final Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(is);
            int i = 1;
            for (final Certificate certificate : certificates) {
                result.setCertificateEntry("cert" + i++, certificate);
            }
        }

        return result;
    }

    @Nonnull
    protected KeyStore loadDefaultKeyStore() throws GeneralSecurityException, IOException {
        final InputStream is = tryOpenDefaultKeyStoreInputStream();
        try {
            return readDefaultKeyStoreFrom(is);
        } finally {
            closeQuietly(is);
        }
    }

    @Nullable
    protected InputStream tryOpenDefaultKeyStoreInputStream() throws IOException {
        final String trustStorePath = getProperty("javax.net.ssl.trustStore");
        if (isNotEmpty(trustStorePath)) {
            return new FileInputStream(trustStorePath);
        }

        final String javaHome = getProperty("java.home");
        final Path jssecacertsFile = Paths.get(javaHome).resolve("lib").resolve("security").resolve("jssecacerts");
        if (isRegularFile(jssecacertsFile)) {
            return newInputStream(jssecacertsFile);
        }
        final Path cacertsFile = Paths.get(javaHome).resolve("lib").resolve("security").resolve("cacerts");
        if (isRegularFile(cacertsFile)) {
            return newInputStream(cacertsFile);
        }
        return null;
    }

    @Nonnull
    protected KeyStore readDefaultKeyStoreFrom(@Nullable InputStream is) throws GeneralSecurityException, IOException {
        final String type = getProperty("javax.net.ssl.trustStoreType", KeyStore.getDefaultType());
        final String provider = getProperty("javax.net.ssl.trustStoreProvider");

        final KeyStore result;
        if (isEmpty(provider)) {
            result = KeyStore.getInstance(type);
        } else {
            result = KeyStore.getInstance(type, provider);
        }

        final String password = getProperty("javax.net.ssl.trustStorePassword");
        result.load(is, isNotEmpty(password) ? password.toCharArray() : null);

        return result;
    }
}