import { StructuredError } from '@agentuity/core';
import * as tui from './tui';
export function isMisconfigured(x) {
    return 'misconfigured' in x && !!x.misconfigured;
}
export function isMissing(x) {
    return 'pending' in x && x.pending === false && 'success' in x && x.success === false;
}
export function isError(x) {
    return 'error' in x && !!x.error;
}
export function isPending(x) {
    return 'pending' in x && x.pending === true && x.success === true;
}
export function isSuccess(x) {
    return x.success == true && !('pending' in x) && !('error' in x) && !('misconfigured' in x);
}
const timeoutMs = 5000;
const DNSTimeoutError = StructuredError('DNSTimeoutError', `DNS lookup timed out after ${timeoutMs}ms`);
async function fetchDNSRecord(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());
        const firstAnswer = result?.Answer?.[0];
        if (firstAnswer) {
            return firstAnswer.data.replace(/\.$/, ''); // DNS records end with . so we remove that
        }
    }
    return 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, 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}`;
    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,
                    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,
                    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")`,
                };
            }
        }
        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) {
                    return {
                        domain,
                        target: proxy,
                        recordType: 'CNAME',
                        success: true,
                    };
                }
                return {
                    domain,
                    target: proxy,
                    recordType: 'CNAME',
                    success: false,
                    misconfigured: `CNAME record is ${result}`,
                };
            }
        }
        catch (ex) {
            const _ex = ex;
            if (_ex.message?.includes('timed out')) {
                return {
                    domain,
                    target: proxy,
                    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,
                    recordType: 'CNAME',
                    success: false,
                    error: errMsg,
                };
            }
        }
        return {
            domain,
            success: false,
            target: proxy,
            recordType: 'CNAME',
            pending: false,
        };
    }));
}
export async function promptForDNS(projectId, domains, config, resumeFn) {
    let paused = false;
    let resume;
    for (;;) {
        const result = await checkCustomDomainForDNS(projectId, domains, config);
        const failed = result.filter((x) => !isSuccess(x));
        if (failed.length) {
            const records = [];
            result.forEach((r) => {
                if (isSuccess(r)) {
                    records.push({
                        domain: r.domain,
                        type: r.recordType,
                        target: r.target,
                        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,
                        type: r.recordType,
                        target: r.target,
                        status: tui.colorWarning(`${tui.ICONS.error} ${r.misconfigured}`),
                    });
                }
                else if (isPending(r)) {
                    records.push({
                        domain: r.domain,
                        type: r.recordType,
                        target: r.target,
                        status: tui.colorWarning('⌛️ Pending'),
                    });
                }
                else if (isMissing(r)) {
                    records.push({
                        domain: r.domain,
                        type: r.recordType,
                        target: r.target,
                        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('Type:')}    ${tui.colorPrimary(record.type)}`);
                console.log(`${tui.colorInfo('Target:')}  ${tui.colorPrimary(record.target)}`);
                console.log(`${tui.colorInfo('Status:')}  ${tui.colorPrimary(record.status)}`);
                console.log();
                linesShown += 6;
            }
            // 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;
    }
}
//# sourceMappingURL=domain.js.map