import { spawn } from 'bun';
import { mkdir, mkdtemp, readdir, realpath, rm, stat } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { ErrorCode } from '../../errors';
import * as tui from '../../tui';
async function runCommand(cmd, cwd) {
    const proc = spawn({
        cmd,
        cwd,
        env: { ...process.env, CI: 'true' },
        stdin: 'ignore',
        stdout: 'inherit',
        stderr: 'inherit',
    });
    await proc.exited;
    return proc.exitCode ?? 1;
}
async function downloadSource(url, targetPath) {
    let lastError;
    for (let attempt = 1; attempt <= 3; attempt++) {
        try {
            const response = await fetch(url, { signal: AbortSignal.timeout(60_000) });
            if (!response.ok) {
                throw new Error(`Download failed with status ${response.status}`);
            }
            await Bun.write(targetPath, await response.arrayBuffer());
            return;
        }
        catch (error) {
            lastError = error;
            if (attempt < 3) {
                tui.info(`Download failed (attempt ${attempt}/3), retrying...`);
            }
        }
    }
    throw lastError instanceof Error ? lastError : new Error('Download failed');
}
function buildDeployArgs(opts) {
    const args = [];
    if (opts.trigger)
        args.push('--trigger', opts.trigger);
    if (opts.event)
        args.push('--event', opts.event);
    if (opts.message)
        args.push('--message', opts.message);
    if (opts.commit)
        args.push('--commit', opts.commit);
    if (opts.commitUrl)
        args.push('--commit-url', opts.commitUrl);
    if (opts.branch)
        args.push('--branch', opts.branch);
    if (opts.repo)
        args.push('--repo', opts.repo);
    if (opts.provider)
        args.push('--provider', opts.provider);
    if (opts.pullRequestNumber !== undefined) {
        args.push('--pull-request-number', String(opts.pullRequestNumber));
    }
    if (opts.pullRequestUrl)
        args.push('--pull-request-url', opts.pullRequestUrl);
    if (opts.logsUrl)
        args.push('--logs-url', opts.logsUrl);
    return args;
}
export async function runCIBuild(opts, _logger) {
    let tempDir = '';
    // Track subprocess exit code so we can clean up in finally before calling process.exit().
    // Bun does not reliably honor process.exitCode, so an explicit process.exit() is required.
    let pendingExitCode;
    try {
        let projectDir;
        if (opts.url) {
            // Download and extract source from URL
            tempDir = await mkdtemp(join(tmpdir(), 'agentuity-ci-build-'));
            const sourceZipPath = join(tempDir, 'source.zip');
            const extractPath = join(tempDir, 'build');
            tui.info('1️⃣ Downloading source code from GitHub...');
            await downloadSource(opts.url, sourceZipPath);
            tui.info('2️⃣ Unzipping source code from GitHub...');
            await mkdir(extractPath, { recursive: true });
            const unzipExit = await runCommand(['unzip', '-q', sourceZipPath, '-d', extractPath], tempDir);
            if (unzipExit !== 0 && unzipExit !== 1) {
                tui.error(`Failed to unzip source archive (exit ${unzipExit})`);
                pendingExitCode = unzipExit;
                return;
            }
            const extractedEntries = await readdir(extractPath, { withFileTypes: true });
            const extractedDirs = extractedEntries.filter((entry) => entry.isDirectory());
            if (extractedDirs.length !== 1) {
                tui.fatal(`Expected one root directory after unzip, found ${extractedDirs.length}`, ErrorCode.BUILD_FAILED);
            }
            const sourceRoot = extractedDirs.at(0);
            if (!sourceRoot) {
                tui.fatal('Could not determine extracted source directory', ErrorCode.BUILD_FAILED);
            }
            const sourceRootDir = join(extractPath, sourceRoot.name);
            projectDir = sourceRootDir;
            if (opts.directory) {
                projectDir = join(sourceRootDir, opts.directory);
            }
            const projectStats = await stat(projectDir).catch(() => null);
            if (!projectStats?.isDirectory()) {
                tui.fatal(`Build directory not found: ${projectDir}`, ErrorCode.CONFIG_INVALID);
            }
            // Resolve symlinks and verify the project dir is within the source root
            const realProjectDir = await realpath(projectDir).catch(() => null);
            const realSourceRoot = await realpath(sourceRootDir).catch(() => null);
            if (!realProjectDir || !realSourceRoot || !realProjectDir.startsWith(realSourceRoot)) {
                tui.fatal('Directory path escapes the source root (path traversal denied)', ErrorCode.CONFIG_INVALID);
            }
            projectDir = realProjectDir;
        }
        else {
            // No URL — use current working directory (source already present, e.g. snapshot-based deploy)
            tui.info('1️⃣ Using local source (no download URL provided)...');
            projectDir = process.cwd();
            if (opts.directory) {
                projectDir = join(projectDir, opts.directory);
            }
        }
        const sdkKey = process.env.AGENTUITY_SDK_KEY;
        if (sdkKey) {
            await Bun.write(join(projectDir, '.env'), `AGENTUITY_SDK_KEY=${sdkKey}\n`);
        }
        tui.info('3️⃣ Installing your project dependencies...');
        const installExit = await runCommand(['bun', 'install'], projectDir);
        if (installExit !== 0) {
            tui.error(`Dependency installation failed (exit ${installExit})`);
            pendingExitCode = installExit;
            return;
        }
        const packageJsonPath = join(projectDir, 'package.json');
        const packageJsonFile = Bun.file(packageJsonPath);
        let scripts;
        if (await packageJsonFile.exists()) {
            const packageJson = (await packageJsonFile.json());
            scripts = packageJson.scripts;
        }
        if (scripts?.predeploy) {
            tui.info('🔧 Running predeploy script...');
            const predeployExit = await runCommand(['bun', 'run', '--bun', 'predeploy'], projectDir);
            if (predeployExit !== 0) {
                tui.error(`Predeploy failed (exit ${predeployExit})`);
                pendingExitCode = predeployExit;
                return;
            }
        }
        tui.info('4️⃣ Deploying your project...');
        // Use the locally installed CLI binary instead of bunx to avoid
        // bunx resolution crashes on certain Bun versions (e.g. arm64 segfaults)
        const localCliBin = join(projectDir, 'node_modules', '.bin', 'agentuity');
        const cliExists = await stat(localCliBin).catch(() => null);
        const deployCmd = cliExists
            ? ['bun', localCliBin, 'deploy', ...buildDeployArgs(opts)]
            : ['bunx', '--bun', 'agentuity', 'deploy', ...buildDeployArgs(opts)];
        tui.info(`Using CLI: ${cliExists ? localCliBin : 'bunx --bun agentuity'}`);
        const deployExit = await runCommand(deployCmd, projectDir);
        if (deployExit !== 0) {
            tui.error(`Deploy failed (exit ${deployExit})`);
            pendingExitCode = deployExit;
            return;
        }
        if (scripts?.postdeploy) {
            tui.info('🔧 Running postdeploy script...');
            const postdeployExit = await runCommand(['bun', 'run', '--bun', 'postdeploy'], projectDir);
            if (postdeployExit !== 0) {
                tui.error(`Postdeploy failed (exit ${postdeployExit})`);
                pendingExitCode = postdeployExit;
                return;
            }
        }
        tui.success('✅ Your project has been built...');
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        tui.fatal(`CI build failed: ${message}`, ErrorCode.BUILD_FAILED);
    }
    finally {
        if (tempDir) {
            await rm(tempDir, { recursive: true, force: true });
        }
        if (pendingExitCode !== undefined) {
            process.exit(pendingExitCode);
        }
    }
}
//# sourceMappingURL=ci.js.map