import { isValidProvider, validateServerName } from "./manage.js";
import { getProviderToken } from "./tokens.js";
import { createProviderWithToken } from "../utils/providerFactory.js";
import { getBareCloudInit } from "../utils/cloudInit.js";
import { getAdapter } from "../adapters/factory.js";
import { findLocalSshKey, generateSshKey, getSshKeyName } from "../utils/sshKey.js";
import { saveServer } from "../utils/config.js";
import { getTemplateDefaults } from "../utils/templates.js";
import { getErrorMessage, mapProviderError } from "../utils/errorMapper.js";
import { assertValidIp, clearKnownHostKey } from "../utils/ssh.js";
import { debugLog } from "../utils/logger.js";
import { IP_WAIT, BOOT_WAIT, BOOT_WAIT_DEFAULT, invalidProviderError } from "../constants.js";
// ─── Helpers ─────────────────────────────────────────────────────────────────
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
function isPendingIp(ip) {
    return !ip || ip === "pending" || ip === "0.0.0.0";
}
export async function uploadSshKeyBestEffort(provider) {
    let publicKey = findLocalSshKey();
    if (!publicKey) {
        process.stderr.write("[provision] No local SSH key found. Generating one...\n");
        publicKey = generateSshKey();
        if (!publicKey) {
            process.stderr.write("[provision] SSH key generation failed. Continuing without SSH key.\n");
            return [];
        }
        process.stderr.write("[provision] SSH key generated (~/.ssh/id_ed25519)\n");
    }
    try {
        const keyId = await provider.uploadSshKey(getSshKeyName(), publicKey);
        return [keyId];
    }
    catch (error) {
        process.stderr.write(`[provision] SSH key upload failed: ${getErrorMessage(error)}. Continuing without SSH key.\n`);
        return [];
    }
}
// ─── Main ────────────────────────────────────────────────────────────────────
export async function provisionServer(config) {
    // 1. Validate provider
    if (!isValidProvider(config.provider)) {
        return {
            success: false,
            error: invalidProviderError(config.provider),
        };
    }
    // 2. Validate name
    const nameError = validateServerName(config.name);
    if (nameError) {
        return { success: false, error: nameError };
    }
    // 3. Resolve region/size — explicit params override template defaults
    const template = config.template || "starter";
    const defaults = getTemplateDefaults(template, config.provider);
    const region = config.region || defaults?.region;
    const size = config.size || defaults?.size;
    if (!region || !size) {
        return {
            success: false,
            error: `Could not resolve region/size for provider "${config.provider}" with template "${template}"`,
            hint: "Provide explicit region and size parameters, or use a valid template",
        };
    }
    // 4. Resolve token
    const token = getProviderToken(config.provider);
    if (!token) {
        return {
            success: false,
            error: `No API token found for ${config.provider}`,
            hint: `Set ${config.provider.toUpperCase()}_TOKEN environment variable`,
        };
    }
    // 5. Create provider instance
    const provider = createProviderWithToken(config.provider, token);
    // 6. Validate token
    try {
        const valid = await provider.validateToken(token);
        if (!valid) {
            return { success: false, error: `Invalid API token for ${config.provider}` };
        }
    }
    catch (error) {
        return {
            success: false,
            error: `Token validation failed: ${getErrorMessage(error)}`,
        };
    }
    // 7. Upload SSH key (best-effort)
    const sshKeyIds = await uploadSshKeyBestEffort(provider);
    // 8. Generate cloud-init
    const modeStr = config.mode || "coolify";
    const isBare = modeStr === "bare";
    const platform = isBare ? undefined : (modeStr === "dokploy" ? "dokploy" : "coolify");
    const sshPublicKey = findLocalSshKey() ?? undefined;
    const cloudInit = platform
        ? getAdapter(platform).getCloudInit(config.name, sshPublicKey)
        : getBareCloudInit(config.name);
    // 9. Create server
    let serverId;
    let serverIp;
    try {
        const result = await provider.createServer({
            name: config.name,
            region,
            size,
            cloudInit,
            sshKeyIds,
        });
        serverId = result.id;
        serverIp = result.ip;
    }
    catch (error) {
        const message = getErrorMessage(error);
        const hint = mapProviderError(error, config.provider);
        return {
            success: false,
            error: `Server creation failed: ${message}`,
            ...(hint ? { hint } : {}),
        };
    }
    // Helper: build ServerRecord with current state (avoids duplication between timeout and success paths)
    const buildRecord = (ip) => ({
        id: serverId,
        name: config.name,
        provider: config.provider,
        ip,
        region,
        size,
        createdAt: new Date().toISOString(),
        mode: isBare ? "bare" : "coolify",
        platform,
    });
    // 10. Wait for running status (provider-specific timing)
    const bootConfig = BOOT_WAIT[config.provider] || BOOT_WAIT_DEFAULT;
    for (let i = 0; i < bootConfig.attempts; i++) {
        try {
            const status = await provider.getServerStatus(serverId);
            if (status === "running")
                break;
        }
        catch (error) {
            // Ignore polling errors, retry
            debugLog?.("provision polling error, retrying", { cause: error });
        }
        if (i === bootConfig.attempts - 1) {
            const totalSec = Math.round((bootConfig.attempts * bootConfig.interval) / 1000);
            await saveServer(buildRecord(isPendingIp(serverIp) ? "pending" : serverIp));
            return {
                success: false,
                error: `Server did not reach running state within ${totalSec}s`,
                hint: `Server saved to config as '${config.name}'. Check with: kastell status ${config.name}`,
            };
        }
        await sleep(bootConfig.interval);
    }
    // 11. Wait for IP assignment (provider-specific timing)
    if (isPendingIp(serverIp)) {
        const ipConfig = IP_WAIT[config.provider] || { attempts: 20, interval: 3000 };
        for (let i = 0; i < ipConfig.attempts; i++) {
            await sleep(ipConfig.interval);
            try {
                const details = await provider.getServerDetails(serverId);
                if (!isPendingIp(details.ip)) {
                    try {
                        assertValidIp(details.ip);
                        serverIp = details.ip;
                        break;
                    }
                    catch (error) {
                        // Invalid IP format, keep polling
                        debugLog?.("invalid IP format during polling", { cause: error });
                    }
                }
            }
            catch (error) {
                // Ignore polling errors, retry
                debugLog?.("provision polling error, retrying", { cause: error });
            }
        }
    }
    else {
        // Validate the IP we already have
        try {
            assertValidIp(serverIp);
        }
        catch (error) {
            process.stderr.write(`[provision] IP validation failed for ${serverIp}, marking as pending\n`);
            debugLog?.("IP validation failed, marking as pending", { cause: error });
            serverIp = "pending";
        }
    }
    // 12. Clear stale known_hosts entry (IP reuse across provision/destroy cycles)
    if (!isPendingIp(serverIp)) {
        clearKnownHostKey(serverIp);
    }
    // 13. Save to config
    const record = buildRecord(serverIp);
    await saveServer(record);
    // 14. Return result
    if (isPendingIp(serverIp)) {
        return {
            success: true,
            server: record,
            hint: `IP address not yet assigned. Check status with: server_info { action: 'status', server: '${config.name}' }`,
        };
    }
    return { success: true, server: record };
}
//# sourceMappingURL=provision.js.map