package net.siegeln.cameleer.saas.certificate;

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

import java.time.Instant;
import java.util.List;
import java.util.UUID;

@Service
public class CertificateService {

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

    private final CertificateManager certManager;
    private final CertificateRepository certRepository;
    private final TenantRepository tenantRepository;

    public CertificateService(CertificateManager certManager,
                              CertificateRepository certRepository,
                              TenantRepository tenantRepository) {
        this.certManager = certManager;
        this.certRepository = certRepository;
        this.tenantRepository = tenantRepository;
    }

    public record CertificateOverview(
        CertificateEntity active,
        CertificateEntity staged,
        CertificateEntity archived
    ) {}

    public CertificateOverview getOverview() {
        return new CertificateOverview(
            certRepository.findByStatus(CertificateEntity.Status.ACTIVE).orElse(null),
            certRepository.findByStatus(CertificateEntity.Status.STAGED).orElse(null),
            certRepository.findByStatus(CertificateEntity.Status.ARCHIVED).orElse(null)
        );
    }

    @Transactional
    public CertValidationResult stage(byte[] certPem, byte[] keyPem, byte[] caBundlePem, String keyPassword, UUID actorId) {
        if (!certManager.isAvailable()) {
            return CertValidationResult.fail(List.of("Certificate management is not available"));
        }

        // Discard any existing staged cert
        certRepository.findByStatus(CertificateEntity.Status.STAGED).ifPresent(certRepository::delete);

        // Stage files and validate
        CertValidationResult result = certManager.stage(certPem, keyPem, caBundlePem, keyPassword);
        if (!result.valid()) {
            return result;
        }

        // Save metadata to DB
        var entity = CertificateEntity.fromInfo(result.info(), CertificateEntity.Status.STAGED);
        entity.setUploadedBy(actorId);
        certRepository.save(entity);

        log.info("Certificate staged by actor {}: subject={}", actorId, result.info().subject());
        return result;
    }

    @Transactional
    public void activate() {
        var staged = certRepository.findByStatus(CertificateEntity.Status.STAGED)
                .orElseThrow(() -> new IllegalStateException("No staged certificate to activate"));

        // File operations: delete archive files, move active -> archive, move staged -> active
        certManager.activate();

        // DB: delete archived, active -> archived, staged -> active
        certRepository.findByStatus(CertificateEntity.Status.ARCHIVED).ifPresent(certRepository::delete);

        certRepository.findByStatus(CertificateEntity.Status.ACTIVE).ifPresent(active -> {
            active.setStatus(CertificateEntity.Status.ARCHIVED);
            active.setArchivedAt(Instant.now());
            certRepository.save(active);
        });

        staged.setStatus(CertificateEntity.Status.ACTIVE);
        staged.setActivatedAt(Instant.now());
        certRepository.save(staged);

        log.info("Certificate activated: subject={}", staged.getSubject());
    }

    @Transactional
    public void restore() {
        var archived = certRepository.findByStatus(CertificateEntity.Status.ARCHIVED)
                .orElseThrow(() -> new IllegalStateException("No archived certificate to restore"));

        if (archived.getNotAfter() != null && archived.getNotAfter().isBefore(Instant.now())) {
            throw new IllegalStateException("Archived certificate has expired and cannot be restored");
        }

        // File operations: swap active <-> archive
        certManager.restore();

        // DB: swap statuses
        var active = certRepository.findByStatus(CertificateEntity.Status.ACTIVE).orElse(null);

        archived.setStatus(CertificateEntity.Status.ACTIVE);
        archived.setActivatedAt(Instant.now());
        archived.setArchivedAt(null);
        certRepository.save(archived);

        if (active != null) {
            active.setStatus(CertificateEntity.Status.ARCHIVED);
            active.setArchivedAt(Instant.now());
            certRepository.save(active);
        }

        log.info("Certificate restored from archive: subject={}", archived.getSubject());
    }

    @Transactional
    public void discardStaged() {
        certManager.discardStaged();
        certRepository.findByStatus(CertificateEntity.Status.STAGED).ifPresent(certRepository::delete);
        log.info("Staged certificate discarded");
    }

    /**
     * Count tenants whose ca_applied_at is before the active cert's activated_at,
     * meaning they haven't picked up the latest CA bundle.
     */
    public long countStaleTenants() {
        var active = certRepository.findByStatus(CertificateEntity.Status.ACTIVE).orElse(null);
        if (active == null || active.getActivatedAt() == null) return 0;
        if (!active.isHasCa()) return 0; // no CA bundle to propagate
        return tenantRepository.countByCaAppliedAtBeforeOrCaAppliedAtIsNull(active.getActivatedAt());
    }

    /**
     * Seed the DB from the filesystem on startup (for bootstrap-generated certs).
     */
    @Transactional
    public void seedFromFilesystem() {
        if (certRepository.findByStatus(CertificateEntity.Status.ACTIVE).isPresent()) {
            return; // Already seeded
        }
        CertificateInfo activeInfo = certManager.getActive();
        if (activeInfo != null) {
            var entity = CertificateEntity.fromInfo(activeInfo, CertificateEntity.Status.ACTIVE);
            entity.setActivatedAt(Instant.now());
            certRepository.save(entity);
            log.info("Seeded certificate metadata from filesystem: subject={}", activeInfo.subject());
        }
    }
}
