/**
 * Certificate Requirement Manager
 *
 * Manages the lifecycle of SSL certificates based on service requirements.
 * Automatically acquires, renews, and cleans up certificates.
 */
import * as plugins from '../plugins.js';
import { logger } from '../logging.js';
import { getErrorMessage } from '../utils/error.js';
import { OneboxDatabase } from '../database/index.js';
import { OneboxSslManager } from './ssl.js';
export class CertRequirementManager {
    database;
    sslManager;
    // Certificate renewal threshold (30 days before expiry)
    RENEWAL_THRESHOLD_MS = 30 * 24 * 60 * 60 * 1000;
    // Certificate cleanup delay (90 days after becoming invalid)
    CLEANUP_DELAY_MS = 90 * 24 * 60 * 60 * 1000;
    constructor(database, sslManager) {
        this.database = database;
        this.sslManager = sslManager;
    }
    /**
     * Process all pending certificate requirements
     * Matches requirements to existing certificates or schedules acquisition
     */
    async processPendingRequirements() {
        try {
            const allRequirements = await this.database.getAllCertRequirements();
            const pendingRequirements = allRequirements.filter((req) => req.status === 'pending');
            logger.info(`Processing ${pendingRequirements.length} pending certificate requirement(s)`);
            for (const requirement of pendingRequirements) {
                try {
                    await this.processRequirement(requirement);
                }
                catch (error) {
                    logger.error(`Failed to process requirement ${requirement.id}: ${getErrorMessage(error)}`);
                }
            }
        }
        catch (error) {
            logger.error(`Failed to process pending requirements: ${getErrorMessage(error)}`);
        }
    }
    /**
     * Process a single certificate requirement
     */
    async processRequirement(requirement) {
        const domain = await this.database.getDomainById(requirement.domainId);
        if (!domain) {
            logger.error(`Domain ${requirement.domainId} not found for requirement ${requirement.id}`);
            return;
        }
        // Construct the full domain name
        const fullDomain = requirement.subdomain
            ? `${requirement.subdomain}.${domain.domain}`
            : domain.domain;
        logger.debug(`Processing requirement for domain: ${fullDomain}`);
        // Check if a valid certificate already exists
        const existingCert = await this.findValidCertificate(domain, requirement.subdomain);
        if (existingCert) {
            // Link existing certificate to requirement
            await this.database.updateCertRequirement(requirement.id, {
                certificateId: existingCert.id,
                status: 'active',
            });
            logger.info(`Linked existing certificate to requirement for ${fullDomain}`);
        }
        else {
            // Schedule certificate acquisition
            await this.acquireCertificate(requirement, domain, fullDomain);
        }
    }
    /**
     * Find a valid certificate for the given domain and subdomain
     */
    async findValidCertificate(domain, subdomain) {
        const certificates = await this.database.getCertificatesByDomain(domain.id);
        const now = Date.now();
        for (const cert of certificates) {
            // Skip invalid or expired certificates
            if (!cert.isValid || cert.expiryDate <= now) {
                continue;
            }
            // Check if certificate covers the required domain
            if (cert.isWildcard && !subdomain) {
                // Wildcard cert covers base domain
                return cert;
            }
            else if (cert.isWildcard && subdomain) {
                // Wildcard cert covers first-level subdomains
                const levels = subdomain.split('.').length;
                if (levels === 1) {
                    return cert;
                }
            }
            else if (!cert.isWildcard && cert.certDomain === domain.domain && !subdomain) {
                // Exact match for base domain
                return cert;
            }
            else if (!cert.isWildcard && subdomain) {
                // Exact match for specific subdomain
                const fullDomain = `${subdomain}.${domain.domain}`;
                if (cert.certDomain === fullDomain) {
                    return cert;
                }
            }
        }
        return null;
    }
    /**
     * Acquire a new certificate for the requirement
     */
    async acquireCertificate(requirement, domain, fullDomain) {
        try {
            logger.info(`Acquiring certificate for ${fullDomain}...`);
            // Determine if we should use wildcard
            const useWildcard = domain.defaultWildcard && !requirement.subdomain;
            // Acquire certificate using SSL manager
            const certData = await this.sslManager.acquireCertificate(domain.domain, useWildcard);
            // Store certificate in database
            const now = Date.now();
            const certificate = await this.database.createCertificate({
                domainId: domain.id,
                certDomain: domain.domain,
                isWildcard: useWildcard,
                certPem: certData.certPem,
                keyPem: certData.keyPem,
                fullchainPem: certData.fullchainPem,
                expiryDate: certData.expiryDate,
                issuer: certData.issuer,
                isValid: true,
                createdAt: now,
                updatedAt: now,
            });
            // Link certificate to requirement
            await this.database.updateCertRequirement(requirement.id, {
                certificateId: certificate.id,
                status: 'active',
            });
            logger.success(`Certificate acquired for ${fullDomain}`);
        }
        catch (error) {
            logger.error(`Failed to acquire certificate for ${fullDomain}: ${getErrorMessage(error)}`);
            throw error;
        }
    }
    /**
     * Check all certificates for renewal
     */
    async checkCertificateRenewal() {
        try {
            const allCertificates = await this.database.getAllCertificates();
            const now = Date.now();
            for (const cert of allCertificates) {
                // Skip invalid certificates
                if (!cert.isValid) {
                    continue;
                }
                // Check if certificate is expired
                if (cert.expiryDate <= now) {
                    // Mark as invalid
                    await this.database.updateCertificate(cert.id, { isValid: false });
                    logger.warn(`Certificate ${cert.id} for ${cert.certDomain} has expired`);
                    continue;
                }
                // Check if certificate needs renewal
                const timeUntilExpiry = cert.expiryDate - now;
                if (timeUntilExpiry <= this.RENEWAL_THRESHOLD_MS) {
                    await this.renewCertificate(cert);
                }
            }
        }
        catch (error) {
            logger.error(`Failed to check certificate renewal: ${getErrorMessage(error)}`);
        }
    }
    /**
     * Renew a certificate
     */
    async renewCertificate(cert) {
        try {
            logger.info(`Renewing certificate for ${cert.certDomain}...`);
            const domain = await this.database.getDomainById(cert.domainId);
            if (!domain) {
                logger.error(`Domain ${cert.domainId} not found for certificate ${cert.id}`);
                return;
            }
            // Mark all requirements using this certificate as renewing
            const requirements = await this.database.getAllCertRequirements();
            const relatedRequirements = requirements.filter((req) => req.certificateId === cert.id);
            for (const req of relatedRequirements) {
                await this.database.updateCertRequirement(req.id, { status: 'renewing' });
            }
            // Acquire new certificate
            const certData = await this.sslManager.acquireCertificate(domain.domain, cert.isWildcard);
            // Update certificate in database
            await this.database.updateCertificate(cert.id, {
                certPem: certData.certPem,
                keyPem: certData.keyPem,
                fullchainPem: certData.fullchainPem,
                expiryDate: certData.expiryDate,
                issuer: certData.issuer,
            });
            // Mark requirements as active again
            for (const req of relatedRequirements) {
                await this.database.updateCertRequirement(req.id, { status: 'active' });
            }
            logger.success(`Certificate renewed for ${cert.certDomain}`);
        }
        catch (error) {
            logger.error(`Failed to renew certificate for ${cert.certDomain}: ${getErrorMessage(error)}`);
        }
    }
    /**
     * Clean up old invalid certificates (90+ days old)
     */
    async cleanupOldCertificates() {
        try {
            const allCertificates = await this.database.getAllCertificates();
            const now = Date.now();
            let deletedCount = 0;
            for (const cert of allCertificates) {
                // Only clean up invalid certificates
                if (!cert.isValid) {
                    // Check if certificate has been invalid for 90+ days
                    const timeSinceExpiry = now - cert.expiryDate;
                    if (timeSinceExpiry >= this.CLEANUP_DELAY_MS) {
                        // Delete from database (PEM content is stored in DB, no files to delete)
                        await this.database.deleteCertificate(cert.id);
                        deletedCount++;
                        logger.info(`Deleted old certificate ${cert.id} for ${cert.certDomain} (expired ${new Date(cert.expiryDate).toISOString()})`);
                    }
                }
            }
            if (deletedCount > 0) {
                logger.info(`Cleaned up ${deletedCount} old certificate(s)`);
            }
        }
        catch (error) {
            logger.error(`Failed to cleanup old certificates: ${getErrorMessage(error)}`);
        }
    }
    /**
     * Get certificate status for a domain
     */
    async getCertificateStatus(domainId) {
        const certificates = await this.database.getCertificatesByDomain(domainId);
        const requirements = await this.database.getCertRequirementsByDomain(domainId);
        const now = Date.now();
        let valid = 0;
        let expiringSoon = 0;
        let expired = 0;
        for (const cert of certificates) {
            if (!cert.isValid) {
                expired++;
            }
            else if (cert.expiryDate <= now) {
                expired++;
            }
            else if (cert.expiryDate - now <= this.RENEWAL_THRESHOLD_MS) {
                expiringSoon++;
            }
            else {
                valid++;
            }
        }
        const pending = requirements.filter((req) => req.status === 'pending').length;
        return { valid, expiringSoon, expired, pending };
    }
}
//# sourceMappingURL=data:application/json;base64, 