package com.cameleer.server.app.http;

import com.cameleer.server.core.http.OutboundHttpProperties;
import com.cameleer.server.core.http.OutboundHttpRequestContext;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class SslContextBuilder {

    public SSLContext build(OutboundHttpProperties systemProps, OutboundHttpRequestContext ctx)
            throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException,
                   CertificateException, IOException {
        SSLContext sslContext = SSLContext.getInstance("TLS");

        if (systemProps.trustAll() || ctx.trustMode() == com.cameleer.server.core.http.TrustMode.TRUST_ALL) {
            sslContext.init(null, new TrustManager[]{new TrustAllManager()}, null);
            return sslContext;
        }

        List<X509Certificate> extraCerts = new ArrayList<>();
        // System-level extras are always merged; per-request paths apply only in TRUST_PATHS mode.
        List<String> paths = new ArrayList<>(systemProps.trustedCaPemPaths());
        if (ctx.trustMode() == com.cameleer.server.core.http.TrustMode.TRUST_PATHS) {
            paths.addAll(ctx.trustedCaPemPaths());
        }
        for (String p : paths) {
            extraCerts.addAll(loadPem(p));
        }

        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(null, null);

        // Load JDK default trust roots
        TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        defaultTmf.init((KeyStore) null);
        int i = 0;
        for (TrustManager tm : defaultTmf.getTrustManagers()) {
            if (tm instanceof X509TrustManager x509tm) {
                for (X509Certificate cert : x509tm.getAcceptedIssuers()) {
                    ks.setCertificateEntry("default-" + (i++), cert);
                }
            }
        }
        // Add configured extras
        for (X509Certificate cert : extraCerts) {
            ks.setCertificateEntry("extra-" + (i++), cert);
        }

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        sslContext.init(null, tmf.getTrustManagers(), null);
        return sslContext;
    }

    private Collection<? extends X509Certificate> loadPem(String path) throws IOException, java.security.cert.CertificateException {
        Path p = Path.of(path);
        if (!Files.exists(p)) {
            throw new IllegalArgumentException("CA file not found: " + path);
        }
        try (var in = Files.newInputStream(p)) {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            @SuppressWarnings("unchecked")
            Collection<X509Certificate> certs = (Collection<X509Certificate>) cf.generateCertificates(in);
            return certs;
        }
    }

    private static final class TrustAllManager implements X509TrustManager {
        @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {}
        @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {}
        @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
    }
}
