package net.siegeln.cameleer.saas.certificate;

import net.siegeln.cameleer.saas.provisioning.DockerCertificateManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.security.MessageDigest;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HexFormat;
import java.util.List;
import java.util.UUID;

@Service
public class TenantCaCertService {

    private static final Logger log = LoggerFactory.getLogger(TenantCaCertService.class);

    private final TenantCaCertRepository caCertRepository;
    private final CertificateManager certManager;

    public TenantCaCertService(TenantCaCertRepository caCertRepository, CertificateManager certManager) {
        this.caCertRepository = caCertRepository;
        this.certManager = certManager;
    }

    public List<TenantCaCertEntity> listForTenant(UUID tenantId) {
        return caCertRepository.findByTenantIdOrderByCreatedAtDesc(tenantId);
    }

    @Transactional
    public TenantCaCertEntity stage(UUID tenantId, String label, byte[] certPem) {
        // Parse and validate
        X509Certificate cert;
        try {
            var cf = CertificateFactory.getInstance("X.509");
            cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certPem));
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid CA certificate PEM: " + e.getMessage());
        }

        String fingerprint;
        try {
            fingerprint = HexFormat.ofDelimiter(":").formatHex(
                MessageDigest.getInstance("SHA-256").digest(cert.getEncoded()));
        } catch (Exception e) {
            throw new RuntimeException("Failed to compute fingerprint", e);
        }

        var entity = new TenantCaCertEntity();
        entity.setTenantId(tenantId);
        entity.setStatus(TenantCaCertEntity.Status.STAGED);
        entity.setLabel(label);
        entity.setSubject(cert.getSubjectX500Principal().getName());
        entity.setIssuer(cert.getIssuerX500Principal().getName());
        entity.setFingerprint(fingerprint);
        entity.setNotBefore(cert.getNotBefore().toInstant());
        entity.setNotAfter(cert.getNotAfter().toInstant());
        entity.setCertPem(new String(certPem));

        var saved = caCertRepository.save(entity);
        log.info("Staged tenant CA cert for tenant {}: subject={}", tenantId, entity.getSubject());
        return saved;
    }

    @Transactional
    public TenantCaCertEntity activate(UUID tenantId, UUID certId) {
        var entity = caCertRepository.findById(certId)
            .orElseThrow(() -> new IllegalArgumentException("CA certificate not found"));
        if (!entity.getTenantId().equals(tenantId)) {
            throw new IllegalArgumentException("CA certificate does not belong to this tenant");
        }
        if (entity.getStatus() != TenantCaCertEntity.Status.STAGED) {
            throw new IllegalStateException("Only staged certificates can be activated");
        }

        entity.setStatus(TenantCaCertEntity.Status.ACTIVE);
        caCertRepository.save(entity);

        rebuildCaBundle();
        log.info("Activated tenant CA cert {} for tenant {}", certId, tenantId);
        return entity;
    }

    @Transactional
    public void delete(UUID tenantId, UUID certId) {
        var entity = caCertRepository.findById(certId)
            .orElseThrow(() -> new IllegalArgumentException("CA certificate not found"));
        if (!entity.getTenantId().equals(tenantId)) {
            throw new IllegalArgumentException("CA certificate does not belong to this tenant");
        }

        boolean wasActive = entity.getStatus() == TenantCaCertEntity.Status.ACTIVE;
        caCertRepository.delete(entity);

        if (wasActive) {
            rebuildCaBundle();
        }
        log.info("Deleted tenant CA cert {} for tenant {}", certId, tenantId);
    }

    /**
     * Rebuild the aggregated ca.pem from all active tenant CAs + platform CA.
     * Uses the .wip atomic swap pattern via CertificateManager.
     */
    public void rebuildCaBundle() {
        if (!certManager.isAvailable()) {
            log.warn("Certificate manager not available — skipping CA bundle rebuild");
            return;
        }

        List<TenantCaCertEntity> allActive = caCertRepository.findAllActive();

        // Collect all PEM certs
        var parts = new ArrayList<String>();

        // Platform CA (from existing ca.pem staged with platform cert, if any)
        // We read the current platform cert's CA from the active cert's staged ca
        // Actually, the platform CA is managed separately by CertificateService.
        // We only aggregate tenant CAs here + whatever platform CA exists.
        byte[] existingPlatformCa = readPlatformCa();
        if (existingPlatformCa != null) {
            parts.add(new String(existingPlatformCa).trim());
        }

        for (var cert : allActive) {
            parts.add(cert.getCertPem().trim());
        }

        if (parts.isEmpty()) {
            // No CAs at all — remove ca.pem
            try {
                var certsDir = getCertsPath();
                if (certsDir != null) {
                    java.nio.file.Files.deleteIfExists(certsDir.resolve("ca.pem"));
                    log.info("Removed ca.pem — no active CA certificates");
                }
            } catch (Exception e) {
                log.warn("Failed to remove ca.pem: {}", e.getMessage());
            }
            return;
        }

        byte[] bundleBytes = String.join("\n", parts).concat("\n").getBytes();

        // Validate the bundle is parseable
        try {
            var cf = CertificateFactory.getInstance("X.509");
            var certs = cf.generateCertificates(new ByteArrayInputStream(bundleBytes));
            if (certs.isEmpty()) {
                log.error("Rebuilt CA bundle contains no valid certificates — aborting");
                return;
            }
            log.info("CA bundle rebuilt with {} certificate(s)", certs.size());
        } catch (Exception e) {
            log.error("Rebuilt CA bundle failed validation — aborting: {}", e.getMessage());
            return;
        }

        // Atomic write via .wip pattern
        try {
            var certsDir = getCertsPath();
            if (certsDir == null) return;
            var wipPath = certsDir.resolve("ca.wip");
            var targetPath = certsDir.resolve("ca.pem");
            java.nio.file.Files.write(wipPath, bundleBytes);
            java.nio.file.Files.move(wipPath, targetPath,
                java.nio.file.StandardCopyOption.REPLACE_EXISTING,
                java.nio.file.StandardCopyOption.ATOMIC_MOVE);
            log.info("CA bundle written to {}", targetPath);
        } catch (Exception e) {
            log.error("Failed to write CA bundle: {}", e.getMessage());
        }
    }

    /** Read the platform CA portion (uploaded with the platform cert, not tenant CAs). */
    private byte[] readPlatformCa() {
        // The platform CA is stored alongside the platform cert by CertificateService.
        // We read it from the cert manager, but we need to distinguish it from the
        // aggregated bundle. For now, we don't separate platform CA from tenant CAs
        // in the file — the rebuild always produces the full bundle.
        // Platform CA would be stored separately if vendor uploaded one with their cert.
        return null; // TODO: track platform CA separately if needed
    }

    private java.nio.file.Path getCertsPath() {
        if (certManager instanceof DockerCertificateManager dcm) {
            return dcm.getCertsDir();
        }
        return null;
    }
}
