import { Resource } from "../resource.js";
import { logger } from "../util/logger.js";
import { handleApiError } from "./api-error.js";
import { extractCloudflareResult } from "./api-response.js";
import { createCloudflareApi, } from "./api.js";
import { updateBotManagement, } from "./zone-bot-management.js";
/**
 * A Cloudflare Zone represents a domain and its configuration settings on Cloudflare.
 * Zones allow you to manage DNS, SSL/TLS, caching, security and other settings for a domain.
 *
 * @example
 * // Create a basic zone with default settings
 * const basicZone = await Zone("example.com", {
 *   name: "example.com",
 *   type: "full",
 *   jumpStart: true
 * });
 *
 * @example
 * // Create a zone with enhanced security settings
 * const secureZone = await Zone("secure.example.com", {
 *   name: "secure.example.com",
 *   type: "full",
 *   settings: {
 *     ssl: "strict",
 *     alwaysUseHttps: "on",
 *     automaticHttpsRewrites: "on",
 *     minTlsVersion: "1.3",
 *     tls13: "zrt"
 *   }
 * });
 *
 * @example
 * // Create a zone with optimized performance settings
 * const fastZone = await Zone("fast.example.com", {
 *   name: "fast.example.com",
 *   settings: {
 *     browserCacheTtl: 7200,
 *     brotli: "on",
 *     zeroRtt: "on",
 *     http2: "on",
 *     http3: "on",
 *     earlyHints: "on"
 *   }
 * });
 *
 * @example
 * // Create a development zone with specific features
 * const devZone = await Zone("dev.example.com", {
 *   name: "dev.example.com",
 *   settings: {
 *     developmentMode: "on",
 *     emailObfuscation: "on",
 *     hotlinkProtection: "on",
 *     ipv6: "on",
 *     websockets: "on"
 *   }
 * });
 *
 * @see https://developers.cloudflare.com/dns/zone-setups/
 */
export const Zone = Resource("cloudflare::Zone", async function (_id, props) {
    // Create Cloudflare API client with automatic account discovery
    const api = await createCloudflareApi(props);
    if (this.phase === "delete") {
        if (this.output?.id && props.delete !== false) {
            const deleteResponse = await api.delete(`/zones/${this.output.id}`);
            if (!deleteResponse.ok && deleteResponse.status !== 404) {
                await handleApiError(deleteResponse, "delete", "zone", this.output.id);
            }
        }
        else {
            logger.warn(`Zone '${props.name}' not found, skipping delete`);
        }
        return this.destroy();
    }
    let zoneData;
    if (this.phase === "update" && this.output?.id) {
        // Get zone details to verify it exists
        const response = await api.get(`/zones/${this.output.id}`);
        if (!response.ok) {
            throw new Error(`Error getting zone '${props.name}': ${response.statusText}`);
        }
        zoneData = (await response.json()).result;
    }
    else {
        const response = await api.post("/zones", {
            name: props.name,
            type: props.type || "full",
            jump_start: props.jumpStart !== false,
            account: {
                id: api.accountId,
            },
        });
        const body = await response.text();
        if (!response.ok) {
            if (response.status === 400 && body.includes("already exists")) {
                // Zone already exists, fetch it instead
                logger.warn(`Zone '${props.name}' already exists during Zone create, adopting it...`);
                const getResponse = await api.get(`/zones?name=${props.name}`);
                if (!getResponse.ok) {
                    throw new Error(`Error fetching existing zone '${props.name}': ${getResponse.statusText}`);
                }
                const zones = (await getResponse.json()).result;
                if (zones.length === 0) {
                    throw new Error(`Zone '${props.name}' does not exist, but the name is reserved for another user.`);
                }
                zoneData = zones[0];
            }
            else {
                throw new Error(`Error creating zone '${props.name}': ${response.statusText}\n${body}`);
            }
        }
        else {
            zoneData = JSON.parse(body).result;
        }
    }
    // Apply defaults to settings
    const settingsToApply = {
        ...props.settings,
        alwaysUseHttps: props.settings?.alwaysUseHttps ?? "on",
    };
    await updateZoneSettings(api, zoneData.id, settingsToApply);
    // Add a small delay to ensure settings are propagated
    // TODO(michael): do we need this?
    // https://github.com/alchemy-run/alchemy/issues/681
    await new Promise((resolve) => setTimeout(resolve, 2000));
    // Update Bot Management configuration if provided
    await updateBotManagement(api, zoneData.id, props.botManagement, this.props?.botManagement);
    return {
        id: zoneData.id,
        name: zoneData.name,
        type: zoneData.type,
        status: zoneData.status,
        paused: zoneData.paused,
        accountId: zoneData.account.id,
        nameservers: zoneData.name_servers,
        originalNameservers: zoneData.original_name_servers,
        createdAt: new Date(zoneData.created_on).getTime(),
        modifiedAt: new Date(zoneData.modified_on).getTime(),
        activatedAt: zoneData.activated_on
            ? new Date(zoneData.activated_on).getTime()
            : null,
        settings: await getZoneSettings(api, zoneData.id),
    };
});
/**
 * Helper function to update zone settings
 */
async function updateZoneSettings(api, zoneId, settings) {
    if (!settings)
        return;
    const settingsMap = {
        ssl: "ssl",
        alwaysUseHttps: "always_use_https",
        automaticHttpsRewrites: "automatic_https_rewrites",
        tls13: "tls_1_3",
        earlyHints: "early_hints",
        emailObfuscation: "email_obfuscation",
        browserCacheTtl: "browser_cache_ttl",
        developmentMode: "development_mode",
        http2: "http2",
        http3: "http3",
        ipv6: "ipv6",
        websockets: "websockets",
        zeroRtt: "0rtt",
        brotli: "brotli",
        hotlinkProtection: "hotlink_protection",
        minTlsVersion: "min_tls_version",
    };
    await Promise.all(Object.entries(settings)
        .filter(([_, value]) => value !== undefined)
        .map(async ([key, value]) => {
        const settingId = settingsMap[key];
        if (!settingId)
            return;
        const response = await api.patch(`/zones/${zoneId}/settings/${settingId}`, {
            value,
        });
        if (!response.ok) {
            const data = await response.text();
            if (response.status === 400 && data.includes("already enabled")) {
                logger.warn(`Warning: Setting '${key}' already enabled`);
                return;
            }
            throw new Error(`Failed to update zone setting ${key}: ${response.statusText}`);
        }
    }));
}
/**
 * Helper function to get current zone settings
 */
async function getZoneSettings(api, zoneId) {
    const settingsResponse = await api.get(`/zones/${zoneId}/settings`);
    if (!settingsResponse.ok) {
        throw new Error(`Failed to fetch zone settings: ${settingsResponse.status} ${settingsResponse.statusText}`);
    }
    const result = (await settingsResponse.json());
    const settingsData = result.result;
    // Helper to get setting value with default
    const getSetting = (id, defaultValue) => {
        const setting = settingsData.find((s) => s.id === id);
        return setting?.value ?? defaultValue;
    };
    return {
        ssl: getSetting("ssl", "off"),
        alwaysUseHttps: getSetting("always_use_https", "off"),
        automaticHttpsRewrites: getSetting("automatic_https_rewrites", "off"),
        tls13: getSetting("tls_1_3", "off"),
        earlyHints: getSetting("early_hints", "off"),
        emailObfuscation: getSetting("email_obfuscation", "off"),
        browserCacheTtl: getSetting("browser_cache_ttl", 14400),
        developmentMode: getSetting("development_mode", "off"),
        http2: getSetting("http2", "on"),
        http3: getSetting("http3", "on"),
        ipv6: getSetting("ipv6", "on"),
        websockets: getSetting("websockets", "on"),
        zeroRtt: getSetting("0rtt", "off"),
        brotli: getSetting("brotli", "on"),
        hotlinkProtection: getSetting("hotlink_protection", "off"),
        minTlsVersion: getSetting("min_tls_version", "1.0"),
    };
}
/**
 * Look up a Cloudflare zone by domain name
 *
 * @param domainName The domain name to look up (e.g., "example.com")
 * @param options Optional API configuration
 * @returns Promise resolving to zone details or null if not found
 *
 * @example
 * // Look up a zone by domain name
 * const zone = await getZoneByDomain(api, "example.com");
 * if (zone) {
 *   console.log(`Zone ID: ${zone.id}`);
 *   console.log(`Nameservers: ${zone.nameservers.join(", ")}`);
 * }
 *
 * @example
 * // Look up a zone with custom API options
 * const zone = await getZoneByDomain(api, "example.com");
 */
export async function getZoneByDomain(api, domainName) {
    const [zone] = await extractCloudflareResult(`get zone for ${domainName}`, api.get(`/zones?name=${encodeURIComponent(domainName)}`));
    return zone;
}
/**
 * Helper function to find zone ID from a hostname
 * Searches for the zone that matches the hostname or its parent domains
 *
 * @param api CloudflareApi instance
 * @param hostname The hostname to find the zone for
 * @returns Promise resolving to the zone ID and zone name
 */
export async function findZoneForHostname(api, hostname) {
    // Remove wildcard prefix if present
    const cleanHostname = hostname.replace(/^\*\./, "");
    // Helper to fetch a page of zones
    const fetchZonePage = async (pageNum) => {
        const response = await api.get(`/zones?per_page=50&page=${pageNum}`);
        if (!response.ok) {
            throw new Error(`Failed to list zones (page ${pageNum}): ${response.statusText}`);
        }
        return response.json();
    };
    // Fetch the first page to get total_pages
    const firstPageData = await fetchZonePage(1);
    const totalPages = firstPageData.result_info?.total_pages ?? 1;
    // Fetch remaining pages concurrently if needed
    const allZones = totalPages > 1
        ? await Promise.all([
            Promise.resolve(firstPageData.result),
            ...Array.from({ length: totalPages - 1 }, (_, i) => fetchZonePage(i + 2).then((data) => data.result)),
        ]).then((results) => results.flat())
        : firstPageData.result;
    // Find the zone that best matches the hostname
    // We look for the longest matching zone name (most specific)
    let bestMatch = null;
    let longestMatch = 0;
    for (const zone of allZones) {
        if (cleanHostname === zone.name ||
            cleanHostname.endsWith(`.${zone.name}`)) {
            if (zone.name.length > longestMatch) {
                longestMatch = zone.name.length;
                bestMatch = { zoneId: zone.id, zoneName: zone.name };
            }
        }
    }
    if (!bestMatch) {
        throw new Error(`Could not find zone for hostname '${hostname}'. Available zones: ${allZones.map((z) => z.name).join(", ")}`);
    }
    return bestMatch;
}
//# sourceMappingURL=zone.js.map