import { StructuredError } from '@agentuity/core';
import { getIONHost } from './config';
import * as tui from './tui';
export function isMisconfigured(x) {
    return 'misconfigured' in x && !!x.misconfigured;
}
export function isMissing(x) {
    return x.success === false && !('error' in x) && !('misconfigured' in x);
}
export function isError(x) {
    return 'error' in x && !!x.error;
}
export function isSuccess(x) {
    return x.success === true;
}
const timeoutMs = 5000;
const DNSTimeoutError = StructuredError('DNSTimeoutError', `DNS lookup timed out after ${timeoutMs}ms`);
async function fetchDNSRecords(name, type) {
    const params = new URLSearchParams();
    params.set('name', name);
    params.set('type', type);
    params.set('_', Date.now().toString());
    const res = await fetch(`https://cloudflare-dns.com/dns-query?${params.toString()}`, {
        headers: {
            Accept: 'application/dns-json',
        },
        // @ts-expect-error - cache is supported by Bun's fetch at runtime but missing from type definitions
        cache: 'no-store',
    });
    if (res.ok) {
        const result = (await res.json());
        // DNS records end with . so we remove that
        return (result?.Answer ?? []).map((a) => a.data.replace(/\.$/, ''));
    }
    return [];
}
async function fetchDNSRecord(name, type) {
    const records = await fetchDNSRecords(name, type);
    return records[0] ?? null;
}
const LOCAL_DNS = 'agentuity.io';
const PRODUCTION_DNS = 'agentuity.run';
/**
 * This function will check for each of the custom domains and make sure they are correctly
 * configured in DNS
 *
 * @param projectId the project id
 * @param config Config
 * @param domains array of domains to check
 * @returns
 */
export async function checkCustomDomainForDNS(projectId, domains, region, config) {
    const suffix = config?.overrides?.api_url?.includes('agentuity.io') ? LOCAL_DNS : PRODUCTION_DNS;
    const id = Bun.hash.xxHash64(projectId).toString(16).padStart(16, '0');
    const proxy = `p${id}.${suffix}`;
    // Resolve the ION host A record(s) so we can validate A records
    // and show the user what IP to point their A record to
    const ionHost = getIONHost(config ?? null, region);
    let ionIPs = [];
    try {
        ionIPs = await fetchDNSRecords(ionHost, 'A');
    }
    catch {
        // If we can't resolve the ION host, A record validation will be skipped
    }
    const aRecordTarget = ionIPs[0] ?? undefined;
    return Promise.all(domains.map(async (domain) => {
        // Detect if user passed a URL instead of a domain name
        if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(domain)) {
            try {
                const url = new URL(domain);
                return {
                    domain,
                    target: proxy,
                    aRecordTarget,
                    recordType: 'CNAME',
                    success: false,
                    error: `Invalid domain format: "${domain}" appears to be a URL. Use just the domain name: "${url.hostname}"`,
                };
            }
            catch {
                return {
                    domain,
                    target: proxy,
                    aRecordTarget,
                    recordType: 'CNAME',
                    success: false,
                    error: `Invalid domain format: "${domain}" appears to be a URL. Use just the domain name without the protocol (e.g., "example.com" not "https://example.com")`,
                };
            }
        }
        // Step 1: Check CNAME record
        try {
            let timeoutId;
            const timeoutPromise = new Promise((_, reject) => {
                timeoutId = setTimeout(() => {
                    reject(new DNSTimeoutError());
                }, timeoutMs);
            });
            const result = await Promise.race([
                fetchDNSRecord(domain, 'CNAME'),
                timeoutPromise,
            ]).finally(() => {
                if (timeoutId)
                    clearTimeout(timeoutId);
            });
            if (result) {
                if (result === proxy) {
                    // DNS is correctly configured
                    return {
                        domain,
                        target: proxy,
                        aRecordTarget,
                        recordType: 'CNAME',
                        success: true,
                    };
                }
                return {
                    domain,
                    target: proxy,
                    aRecordTarget,
                    recordType: 'CNAME',
                    success: false,
                    misconfigured: `CNAME record points to ${result}`,
                };
            }
        }
        catch (ex) {
            const _ex = ex;
            if (_ex.message?.includes('timed out')) {
                return {
                    domain,
                    target: proxy,
                    aRecordTarget,
                    recordType: 'CNAME',
                    success: false,
                    error: `DNS lookup timed out after 5 seconds. Please check your DNS configuration.`,
                };
            }
            if (_ex.code !== 'ENOTFOUND') {
                const errMsg = ex instanceof Error
                    ? ex.message
                    : typeof ex === 'string'
                        ? ex
                        : JSON.stringify(ex);
                return {
                    domain,
                    target: proxy,
                    aRecordTarget,
                    recordType: 'CNAME',
                    success: false,
                    error: errMsg,
                };
            }
            // ENOTFOUND: no CNAME record exists, fall through to A record check
        }
        // Step 2: Check A record (supports apex domains and ALIAS/ANAME/CNAME-flattening)
        if (ionIPs.length > 0) {
            try {
                let aTimeoutId;
                const aTimeoutPromise = new Promise((_, reject) => {
                    aTimeoutId = setTimeout(() => {
                        reject(new DNSTimeoutError());
                    }, timeoutMs);
                });
                const domainARecords = await Promise.race([
                    fetchDNSRecords(domain, 'A'),
                    aTimeoutPromise,
                ]).finally(() => {
                    if (aTimeoutId)
                        clearTimeout(aTimeoutId);
                });
                if (domainARecords.length > 0) {
                    const matching = domainARecords.some((a) => ionIPs.includes(a));
                    if (matching) {
                        // DNS is correctly configured
                        return {
                            domain,
                            target: proxy,
                            aRecordTarget,
                            recordType: 'A',
                            success: true,
                        };
                    }
                    return {
                        domain,
                        target: proxy,
                        aRecordTarget,
                        recordType: 'A',
                        success: false,
                        misconfigured: `A record points to ${domainARecords[0]}, expected ${aRecordTarget}`,
                    };
                }
            }
            catch {
                // A record check failed, fall through to missing
            }
        }
        return {
            domain,
            success: false,
            target: proxy,
            aRecordTarget,
            recordType: 'CNAME',
        };
    }));
}
export async function promptForDNS(projectId, domains, region, config, resumeFn) {
    let paused = false;
    let resume;
    for (;;) {
        const result = await checkCustomDomainForDNS(projectId, domains, region, config);
        const failed = result.filter((x) => !isSuccess(x));
        if (failed.length) {
            const records = [];
            result.forEach((r) => {
                if (isSuccess(r)) {
                    records.push({
                        domain: r.domain,
                        cnameTarget: r.target,
                        aRecordTarget: r.aRecordTarget,
                        status: tui.colorSuccess(`${tui.ICONS.success} Configured`),
                    });
                }
            });
            if (!paused) {
                resume = resumeFn?.();
                paused = true;
            }
            tui.error(`You have ${tui.plural(failed.length, 'a ', '')}DNS configuration ${tui.plural(failed.length, 'issue', 'issues')} that must be resolved before deploying:`);
            for (const r of failed) {
                if (isError(r)) {
                    resume?.();
                    throw new Error(r.error);
                }
                else if (isMisconfigured(r)) {
                    records.push({
                        domain: r.domain,
                        cnameTarget: r.target,
                        aRecordTarget: r.aRecordTarget,
                        status: tui.colorWarning(`${tui.ICONS.error} ${r.misconfigured}`),
                    });
                }
                else if (isMissing(r)) {
                    records.push({
                        domain: r.domain,
                        cnameTarget: r.target,
                        aRecordTarget: r.aRecordTarget,
                        status: tui.colorError(`${tui.ICONS.error} Missing`),
                    });
                }
            }
            let linesShown = 2; // header + footer
            for (const record of records) {
                console.log();
                console.log(`${tui.colorInfo('Domain:')}  ${tui.colorPrimary(record.domain)}`);
                console.log(`${tui.colorInfo('CNAME:')}   ${tui.colorPrimary(record.cnameTarget)}`);
                if (record.aRecordTarget) {
                    console.log(`${tui.colorInfo('A:')}       ${tui.colorPrimary(record.aRecordTarget)}`);
                }
                console.log(`${tui.colorInfo('Status:')}  ${tui.colorPrimary(record.status)}`);
                console.log();
                linesShown += record.aRecordTarget ? 6 : 5;
            }
            // await tui.waitForAnyKey('Press any key to check again or ctrl+c to cancel...');
            await tui.spinner({
                message: 'Checking again in 5s...',
                clearOnSuccess: true,
                callback: () => {
                    return Bun.sleep(5000);
                },
            });
            tui.clearLastLines(linesShown);
            linesShown = 0;
            continue;
        }
        tui.clearLastLines(1);
        resume?.();
        break;
    }
}
/**
 * Trigger TLS certificate provisioning for custom domains by making HTTPS requests.
 * This causes the Ion proxy to initiate Let's Encrypt certificate issuance.
 * Fire-and-forget — failures are silently ignored since certs will be provisioned
 * on first real request if this doesn't succeed.
 */
export async function triggerTLSProvisioning(domains) {
    await Promise.allSettled(domains.map((domain) => fetch(`https://${domain}`, {
        method: 'HEAD',
        signal: AbortSignal.timeout(10000),
        redirect: 'manual',
    }).catch(() => {
        // Silently ignore — cert will be provisioned on first real request
    })));
}
//# sourceMappingURL=domain.js.map