"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeDomain = normalizeDomain;
exports.certificateArtifacts = certificateArtifacts;
exports.buildTlsDynamicConfig = buildTlsDynamicConfig;
exports.ensureRootCaAliases = ensureRootCaAliases;
exports.ensureWildcardCertificate = ensureWildcardCertificate;
exports.createDomainCertificate = createDomainCertificate;
exports.removeDomainCertificate = removeDomainCertificate;
exports.listDomainCertificates = listDomainCertificates;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const crypto_1 = require("crypto");
const mkcert = __importStar(require("@mkcert/node"));
const path_helpers_1 = require("../utils/path-helpers");
const domainLabel = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
function normalizeDomain(domain) {
    const value = domain.trim();
    if (!value) {
        throw new Error('Domain is required');
    }
    if (value.startsWith('*.')) {
        throw new Error('Provide the base domain only. Wildcard certificates are created automatically.');
    }
    if (value.includes('..')) {
        throw new Error(`Invalid domain '${domain}'`);
    }
    const labels = value.split('.');
    if (labels.length < 2 || labels.some((label) => !domainLabel.test(label))) {
        throw new Error(`Invalid domain '${domain}'`);
    }
    return value;
}
function certificateArtifacts(projectDir, domain) {
    const normalized = normalizeDomain(domain);
    const certsDir = path.join(projectDir, 'certs');
    const dynamicDir = path.join(projectDir, 'configuration', 'traefik', 'dynamic');
    return {
        domain: normalized,
        wildcardHost: `*.${normalized}`,
        certFile: path.join(certsDir, `${normalized}.crt`),
        keyFile: path.join(certsDir, `${normalized}.key`),
        dynamicConfigFile: path.join(dynamicDir, `${normalized}.ssl.yml`)
    };
}
function buildTlsDynamicConfig(domain) {
    const normalized = normalizeDomain(domain);
    return [
        'tls:',
        '  certificates:',
        `    - certFile: /ssl/${normalized}.crt`,
        `      keyFile: /ssl/${normalized}.key`,
        ''
    ].join('\n');
}
function ensureExpectedDynamicConfig(dynamicConfigFile, domain) {
    const normalized = normalizeDomain(domain);
    const content = fs.readFileSync(dynamicConfigFile, 'utf8');
    if (!content.includes(`certFile: /ssl/${normalized}.crt`) || !content.includes(`keyFile: /ssl/${normalized}.key`)) {
        throw new Error(`${dynamicConfigFile} already exists but does not match the expected Traefik TLS configuration.`);
    }
}
function ensureRootCaAliases(certsDir) {
    const aliases = [
        { source: 'rootCA.pem', target: 'rootCA.crt' },
        { source: 'rootCA-key.pem', target: 'rootCA.key' }
    ];
    const result = { copied: [], warnings: [] };
    for (const alias of aliases) {
        const source = path.join(certsDir, alias.source);
        const target = path.join(certsDir, alias.target);
        if (fs.existsSync(target)) {
            continue;
        }
        if (fs.existsSync(source)) {
            try {
                fs.copyFileSync(source, target);
                result.copied.push(target);
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                result.warnings.push(`Could not create ${target} from ${source}: ${message}`);
            }
        }
    }
    return result;
}
async function ensureWildcardCertificate(projectDir, domain, options = {}) {
    const artifacts = certificateArtifacts(projectDir, domain);
    const certExists = fs.existsSync(artifacts.certFile);
    const keyExists = fs.existsSync(artifacts.keyFile);
    if (certExists !== keyExists) {
        throw new Error(`Incomplete certificate pair for '${artifacts.domain}'. Expected both ${artifacts.certFile} and ${artifacts.keyFile}.`);
    }
    path_helpers_1.pathHelpers.ensureDir(path.dirname(artifacts.certFile));
    let certificateChanged = false;
    if (!certExists && !keyExists) {
        await mkcert.generate({
            install: Boolean(options.installLocalCa),
            hosts: [artifacts.wildcardHost],
            certFile: artifacts.certFile,
            keyFile: artifacts.keyFile,
            caroot: path.dirname(artifacts.certFile)
        });
        ensureRootCaAliases(path.dirname(artifacts.certFile));
        certificateChanged = true;
    }
    else {
        ensureRootCaAliases(path.dirname(artifacts.certFile));
    }
    return {
        artifacts,
        certificateChanged,
        dynamicConfigChanged: false
    };
}
async function createDomainCertificate(projectDir, domain) {
    const certificateResult = await ensureWildcardCertificate(projectDir, domain);
    const { artifacts } = certificateResult;
    path_helpers_1.pathHelpers.ensureDir(path.dirname(artifacts.dynamicConfigFile));
    let dynamicConfigChanged = false;
    if (fs.existsSync(artifacts.dynamicConfigFile)) {
        ensureExpectedDynamicConfig(artifacts.dynamicConfigFile, artifacts.domain);
    }
    else {
        fs.writeFileSync(artifacts.dynamicConfigFile, buildTlsDynamicConfig(artifacts.domain), 'utf8');
        dynamicConfigChanged = true;
    }
    return {
        artifacts,
        certificateChanged: certificateResult.certificateChanged,
        dynamicConfigChanged
    };
}
function removeDomainCertificate(projectDir, domain) {
    const artifacts = certificateArtifacts(projectDir, domain);
    let certificateChanged = false;
    let dynamicConfigChanged = false;
    for (const filePath of [artifacts.certFile, artifacts.keyFile]) {
        if (fs.existsSync(filePath)) {
            fs.unlinkSync(filePath);
            certificateChanged = true;
        }
    }
    if (fs.existsSync(artifacts.dynamicConfigFile)) {
        fs.unlinkSync(artifacts.dynamicConfigFile);
        dynamicConfigChanged = true;
    }
    return {
        artifacts,
        certificateChanged,
        dynamicConfigChanged
    };
}
function parseSubjectAltName(value) {
    if (!value) {
        return [];
    }
    return value
        .split(',')
        .map((entry) => entry.trim())
        .map((entry) => entry.replace(/^DNS:/i, '').replace(/^IP Address:/i, ''))
        .filter(Boolean);
}
function parseIssuer(value) {
    const parts = value.split('\n').map((part) => part.trim()).filter(Boolean);
    const organization = parts.find((part) => part.startsWith('O='));
    if (organization) {
        return organization.slice(2);
    }
    const commonName = parts.find((part) => part.startsWith('CN='));
    return commonName ? commonName.slice(3) : parts.join(', ');
}
function formatDate(value) {
    return value.toISOString().slice(0, 10);
}
function formatExpiry(validTo, now) {
    const milliseconds = validTo.getTime() - now.getTime();
    if (milliseconds < 0) {
        return 'expired';
    }
    return `${Math.ceil(milliseconds / 86400000)}d`;
}
function listDomainCertificates(projectDir, now = new Date()) {
    const certsDir = path.join(projectDir, 'certs');
    if (!fs.existsSync(certsDir)) {
        return [];
    }
    return fs.readdirSync(certsDir)
        .filter((fileName) => fileName.endsWith('.crt') && fileName !== 'rootCA.crt')
        .sort((left, right) => left.localeCompare(right))
        .map((fileName) => {
        const domain = path.basename(fileName, '.crt');
        const artifacts = certificateArtifacts(projectDir, domain);
        try {
            const certificate = new crypto_1.X509Certificate(fs.readFileSync(artifacts.certFile));
            const validTo = new Date(certificate.validTo);
            const keyExists = fs.existsSync(artifacts.keyFile);
            const isValid = keyExists && validTo.getTime() >= now.getTime();
            return {
                status: isValid ? '✓' : '✗',
                sans: parseSubjectAltName(certificate.subjectAltName),
                issuer: parseIssuer(certificate.issuer),
                validUntil: formatDate(validTo),
                expiry: formatExpiry(validTo, now),
                certFile: artifacts.certFile,
                keyFile: artifacts.keyFile,
                dynamicConfigFile: artifacts.dynamicConfigFile
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return {
                status: '✗',
                sans: [],
                issuer: '-',
                validUntil: '-',
                expiry: 'invalid',
                certFile: artifacts.certFile,
                keyFile: artifacts.keyFile,
                dynamicConfigFile: artifacts.dynamicConfigFile,
                error: message
            };
        }
    });
}
//# sourceMappingURL=cert-manager.js.map