import { Resource } from "../resource.js";
import { logger } from "../util/logger.js";
import { handleApiError } from "./api-error.js";
import { createCloudflareApi, } from "./api.js";
import { findZoneForHostname } from "./zone.js";
/**
 * Creates and manages Cloudflare Advanced Certificate Packs.
 *
 * Advanced Certificate Packs provide flexible SSL/TLS certificates with
 * multiple Certificate Authority options, custom validity periods, and
 * support for up to 50 hostnames per certificate.
 *
 * **Important Notes:**
 * - Requires a paid Cloudflare plan (not available on Free plans)
 * - Certificate provisioning can take up to 10 minutes
 * - Most properties are immutable after creation (only cloudflareBranding can be updated)
 * - To change immutable properties, you must delete and recreate the certificate pack
 *
 * @example
 * // Create a basic certificate pack with Let's Encrypt
 * const basicCert = await CertificatePack("my-cert", {
 *   zone: myZone,
 *   certificateAuthority: "lets_encrypt",
 *   hosts: ["example.com", "www.example.com"],
 *   validationMethod: "txt",
 *   validityDays: 90
 * });
 *
 * @example
 * // Create an enterprise certificate with Google Trust Services
 * const enterpriseCert = await CertificatePack("enterprise-cert", {
 *   zone: "example.com",
 *   certificateAuthority: "google",
 *   hosts: ["example.com", "*.example.com", "api.example.com"],
 *   validationMethod: "txt",
 *   validityDays: 365,
 *   cloudflareBranding: true
 * });
 *
 * @example
 * // Create a wildcard certificate with SSL.com
 * const wildcardCert = await CertificatePack("wildcard-cert", {
 *   zone: myZone,
 *   certificateAuthority: "ssl_com",
 *   hosts: ["example.com", "*.example.com"],
 *   validationMethod: "email",
 *   validityDays: 365
 * });
 *
 * @example
 * // Create a certificate for multiple subdomains
 * const multiDomainCert = await CertificatePack("multi-cert", {
 *   zone: "example.com",
 *   certificateAuthority: "lets_encrypt",
 *   hosts: [
 *     "example.com",
 *     "www.example.com",
 *     "api.example.com",
 *     "admin.example.com",
 *     "blog.example.com"
 *   ],
 *   validationMethod: "http",
 *   validityDays: 90
 * });
 *
 * @see https://developers.cloudflare.com/api/resources/ssl/subresources/certificate_packs/
 */
export const CertificatePack = Resource("cloudflare::CertificatePack", async function (_id, props) {
    // Create Cloudflare API client with automatic account discovery
    const api = await createCloudflareApi(props);
    // Resolve zone ID and zone name
    let zoneId;
    let zoneName;
    if (props.zone) {
        // Zone provided - use it
        if (typeof props.zone === "string") {
            zoneId = props.zone;
            // Try to get zone name from API for better error messages
            try {
                const zoneResponse = await api.get(`/zones/${zoneId}`);
                if (zoneResponse.ok) {
                    const zoneData = (await zoneResponse.json());
                    zoneName = zoneData.result.name;
                }
                else {
                    zoneName = zoneId; // Fallback to ID
                }
            }
            catch {
                zoneName = zoneId; // Fallback to ID
            }
        }
        else {
            zoneId = props.zone.id;
            zoneName = props.zone.name || props.zone.id;
        }
    }
    else {
        // Auto-infer zone from the first host
        if (props.hosts.length === 0) {
            throw new Error("At least one host must be specified when zone is not provided");
        }
        logger.log(`Auto-inferring zone from hostname: ${props.hosts[0]}`);
        const zoneInfo = await findZoneForHostname(api, props.hosts[0]);
        zoneId = zoneInfo.zoneId;
        zoneName = zoneInfo.zoneName;
        logger.log(`Auto-inferred zone: ${zoneName} (${zoneId})`);
    }
    if (this.phase === "delete") {
        if (this.output?.id && props.delete !== false) {
            const deleteResponse = await api.delete(`/zones/${zoneId}/ssl/certificate_packs/${this.output.id}`);
            const deleteResponseJson = (await deleteResponse.json());
            const isCurrentlyDeleting = deleteResponse.status === 400 &&
                deleteResponseJson.errors[0].code === 1406;
            if (!deleteResponse.ok &&
                deleteResponse.status !== 404 &&
                !isCurrentlyDeleting) {
                await handleApiError(deleteResponse, "delete", "certificate pack", this.output.id);
            }
        }
        else {
            logger.warn("Certificate pack not found, skipping delete");
        }
        return this.destroy();
    }
    if (this.phase === "update" && this.output?.id) {
        this.replace();
    }
    // Create new certificate pack
    if (props.hosts.length === 0) {
        throw new Error("At least one host must be specified");
    }
    if (props.hosts.length > 50) {
        throw new Error("Maximum 50 hosts are allowed per certificate pack");
    }
    // Validate that zone apex is included
    const hasZoneApex = props.hosts.some((host) => host === zoneName || (zoneName && host === zoneName));
    if (!hasZoneApex && zoneName) {
        logger.warn(`Zone apex '${zoneName}' is not included in hosts. This may cause certificate validation issues.`);
    }
    // Check for existing certificate pack that matches our configuration
    const existingPack = await findMatchingCertificatePack(api, zoneId, props);
    if (existingPack) {
        // Adopt the existing certificate pack
        logger.log(`Adopting existing certificate pack ${existingPack.id} instead of creating a new one`);
        return {
            id: existingPack.id,
            certificateAuthority: existingPack.certificate_authority,
            cloudflareBranding: props.cloudflareBranding ?? false,
            hosts: existingPack.hosts,
            status: existingPack.status,
            type: existingPack.type,
            validationMethod: existingPack.validation_method,
            validityDays: existingPack.validity_days,
            zoneId,
            zoneName: zoneName,
        };
    }
    logger.log(`Creating certificate pack with ${props.hosts.length} hosts using ${props.certificateAuthority}`);
    const createResponse = await api.post(`/zones/${zoneId}/ssl/certificate_packs/order`, {
        certificate_authority: props.certificateAuthority,
        cloudflare_branding: props.cloudflareBranding || false,
        hosts: props.hosts,
        type: props.type || "advanced",
        validation_method: props.validationMethod,
        validity_days: props.validityDays,
    });
    if (!createResponse.ok) {
        const errorText = await createResponse.text();
        // Provide helpful error messages for common issues
        if (errorText.includes("subscription")) {
            throw new Error(`Failed to create certificate pack: Advanced Certificate Packs require a paid Cloudflare plan. Please upgrade your subscription to use this feature.\n\nOriginal error: ${errorText}`);
        }
        if (errorText.includes("quota")) {
            throw new Error(`Failed to create certificate pack: Certificate pack quota exceeded. Please check your account limits.\n\nOriginal error: ${errorText}`);
        }
        // Throw generic error for other cases
        throw new Error(`Failed to create certificate pack: ${createResponse.statusText}\n\n${errorText}`);
    }
    const createdPack = (await createResponse.json()).result;
    logger.log(`Certificate pack created with ID ${createdPack.id}. Status: ${createdPack.status}. Note: Certificate provisioning can take up to 10 minutes.`);
    return {
        id: createdPack.id,
        certificateAuthority: createdPack.certificate_authority,
        cloudflareBranding: props.cloudflareBranding ?? false,
        hosts: createdPack.hosts,
        status: createdPack.status,
        type: createdPack.type,
        validationMethod: createdPack.validation_method,
        validityDays: createdPack.validity_days,
        zoneId,
        zoneName: zoneName,
    };
});
/**
 * Helper function to wait for certificate pack to reach active status
 * Useful for testing or when you need to ensure the certificate is ready
 *
 * @param api CloudflareApi instance
 * @param zoneId Zone ID
 * @param certificatePackId Certificate pack ID
 * @param timeoutMs Maximum time to wait in milliseconds (default: 15 minutes)
 * @returns Promise resolving to the final certificate pack status
 *
 * @example
 * // Wait for certificate to become active
 * const finalStatus = await waitForCertificatePackActive(
 *   api,
 *   zoneId,
 *   certificatePack.id,
 *   10 * 60 * 1000 // 10 minutes
 * );
 * console.log(`Certificate pack is now: ${finalStatus}`);
 */
export async function waitForCertificatePackActive(api, zoneId, certificatePackId, timeoutMs = 15 * 60 * 1000) {
    const startTime = Date.now();
    const pollInterval = 30 * 1000; // Poll every 30 seconds
    while (Date.now() - startTime < timeoutMs) {
        const response = await api.get(`/zones/${zoneId}/ssl/certificate_packs/${certificatePackId}`);
        if (!response.ok) {
            throw new Error(`Failed to check certificate pack status: ${response.statusText}`);
        }
        const pack = (await response.json()).result;
        // Return immediately if active or in a final error state
        if (pack.status === "active") {
            return pack.status;
        }
        if (pack.status.includes("timed_out") ||
            pack.status === "expired" ||
            pack.status === "deleted") {
            return pack.status;
        }
        // Wait before next poll
        await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }
    throw new Error(`Certificate pack did not become active within ${timeoutMs / 1000 / 60} minutes`);
}
/**
 * Helper function to find existing certificate packs that match the given configuration
 * Used to adopt existing certificates instead of creating duplicates
 *
 * @param api CloudflareApi instance
 * @param zoneId Zone ID to search in
 * @param props Certificate pack properties to match
 * @returns Promise resolving to matching certificate pack or null if none found
 */
async function findMatchingCertificatePack(api, zoneId, props) {
    const response = await api.get(`/zones/${zoneId}/ssl/certificate_packs?status=all`);
    if (!response.ok) {
        throw new Error(`Failed to list certificate packs: ${response.statusText}`);
    }
    const packsData = (await response.json());
    // Find a certificate pack that matches our configuration
    for (const pack of packsData.result) {
        // Skip deleted or expired packs
        if (pack.status === "deleted" || pack.status === "expired") {
            continue;
        }
        // Check if the configuration matches
        if (pack.certificate_authority === props.certificateAuthority &&
            pack.validation_method === props.validationMethod &&
            pack.validity_days === props.validityDays &&
            pack.type === (props.type || "advanced")) {
            // Check if all requested hosts are covered by this certificate pack
            const packHosts = new Set(pack.hosts);
            const allHostsCovered = props.hosts.every((host) => packHosts.has(host));
            if (allHostsCovered) {
                // If Cloudflare branding is enabled, ensure that the cert pack is cloudflare branded`
                if (props.cloudflareBranding &&
                    !pack.hosts.some((host) => host.endsWith("sni.cloudflaressl.com"))) {
                    continue;
                }
                logger.log(`Found existing certificate pack ${pack.id} that covers all requested hosts`);
                return pack;
            }
        }
    }
    return null;
}
//# sourceMappingURL=certificate-pack.js.map