package com.codebyte.api.config.ssl;

import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.UUID;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.ssl.SslBundleRegistrar;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundleKey;
import org.springframework.boot.ssl.SslBundleRegistry;
import org.springframework.boot.ssl.SslStoreBundle;

/**
 * Registers the {@code server} SSL bundle from the cert/CA/key trio (§6.2 preferred option). The
 * assembled chain and private key are placed in an <strong>in-memory</strong> PKCS12 keystore — no
 * temp files, works on a read-only container filesystem. When SSL is disabled this registers
 * nothing.
 */
public class PemSslBundleRegistrar implements SslBundleRegistrar {

    static final String BUNDLE_NAME = "server";
    private static final String ALIAS = "server";
    private static final Logger log = LoggerFactory.getLogger(PemSslBundleRegistrar.class);

    private final SslProperties properties;

    public PemSslBundleRegistrar(SslProperties properties) {
        this.properties = properties;
    }

    @Override
    public void registerBundles(SslBundleRegistry registry) {
        if (!properties.enabled()) {
            return;
        }
        PemSslMaterial material = new PemSslLoader(properties).load();
        logLoaded(material);
        registry.registerBundle(BUNDLE_NAME, toBundle(material));
    }

    private SslBundle toBundle(PemSslMaterial material) {
        // A random in-memory password protects the key entry; it never leaves this JVM.
        String keyPassword = UUID.randomUUID().toString();
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(null, null);
            keyStore.setKeyEntry(
                    ALIAS, material.privateKey(), keyPassword.toCharArray(), material.chainArray());
            SslStoreBundle stores = SslStoreBundle.of(keyStore, keyPassword, null);
            return SslBundle.of(stores, SslBundleKey.of(keyPassword, ALIAS));
        } catch (Exception e) {
            throw new SslConfigurationException(
                    "failed to build the in-memory keystore: " + e.getMessage(), e);
        }
    }

    private void logLoaded(PemSslMaterial material) {
        X509Certificate leaf = material.leaf();
        String sans;
        try {
            sans =
                    leaf.getSubjectAlternativeNames() == null
                            ? "none"
                            : leaf.getSubjectAlternativeNames().stream()
                                    .map(entry -> String.valueOf(entry.get(1)))
                                    .collect(Collectors.joining(", "));
        } catch (Exception e) {
            sans = "unavailable";
        }
        log.info(
                "TLS bundle '{}' loaded: leaf subject='{}', SANs=[{}], issuer='{}', serial={}, "
                        + "notAfter={}, chainCertificates={}",
                BUNDLE_NAME,
                leaf.getSubjectX500Principal().getName(),
                sans,
                leaf.getIssuerX500Principal().getName(),
                leaf.getSerialNumber(),
                leaf.getNotAfter(),
                material.chain().size());
    }
}
