import { spawn } from "child_process";
import * as fs from "fs";
import * as path from "path";
import { parseConfig } from "@specific/config";
import { PortAllocator, startDatabase, resolveEnvForExec, InstanceStateManager, } from "../lib/dev/index.js";
import { prepareSecrets } from "../lib/secrets/index.js";
function findRequiredDatabases(service) {
    const requiredNames = new Set();
    if (service.env) {
        for (const value of Object.values(service.env)) {
            if (typeof value === "object" && value.type === "database") {
                requiredNames.add(value.name);
            }
        }
    }
    return Array.from(requiredNames);
}
export async function execCommand(serviceName, command) {
    if (command.length === 0) {
        console.error("Error: No command provided. Usage: specific exec <service> -- <command>");
        process.exit(1);
    }
    const startedDatabases = [];
    let ownsInstances = false;
    let stateManager = null;
    const cleanup = async () => {
        if (ownsInstances) {
            for (const db of startedDatabases) {
                await db.stop().catch(() => { });
            }
            if (stateManager) {
                await stateManager.releaseOwnership().catch(() => { });
            }
        }
    };
    // Load config
    const configPath = path.join(process.cwd(), "specific.hcl");
    if (!fs.existsSync(configPath)) {
        console.error("Error: No specific.hcl found in current directory");
        process.exit(1);
    }
    let config;
    try {
        const hcl = fs.readFileSync(configPath, "utf-8");
        config = await parseConfig(hcl);
    }
    catch (err) {
        console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
        process.exit(1);
    }
    // Find service
    const service = config.services.find((s) => s.name === serviceName);
    if (!service) {
        const available = config.services.map((s) => s.name).join(", ");
        console.error(`Error: Service "${serviceName}" not found. Available services: ${available || "none"}`);
        process.exit(1);
    }
    // Identify required databases
    const requiredDbNames = findRequiredDatabases(service);
    const databases = new Map();
    // Check for existing instances
    stateManager = new InstanceStateManager(process.cwd());
    await stateManager.cleanStaleState();
    const existingInstances = await stateManager.getExistingInstances();
    if (requiredDbNames.length > 0) {
        if (existingInstances) {
            // Use existing instances
            console.error("Using existing database instances...");
            for (const dbName of requiredDbNames) {
                const dbState = existingInstances.databases[dbName];
                if (!dbState) {
                    console.error(`Error: Required database "${dbName}" not found in running instances`);
                    process.exit(1);
                }
                // Create a DatabaseInstance that doesn't own the underlying database
                databases.set(dbName, {
                    name: dbName,
                    engine: dbState.engine,
                    port: dbState.port,
                    host: dbState.host,
                    user: dbState.user,
                    password: dbState.password,
                    dbName: dbState.dbName,
                    url: dbState.url,
                    async stop() {
                        // No-op: we don't own this instance
                    },
                });
            }
        }
        else {
            // No existing instances - claim ownership and start our own
            console.error("Starting databases...");
            try {
                await stateManager.claimOwnership("exec");
                ownsInstances = true;
            }
            catch (err) {
                console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
                process.exit(1);
            }
            const portAllocator = new PortAllocator();
            for (const dbName of requiredDbNames) {
                const dbConfig = config.databases.find((d) => d.name === dbName);
                if (!dbConfig) {
                    console.error(`Error: Database "${dbName}" referenced by service "${serviceName}" not found in config`);
                    await cleanup();
                    process.exit(1);
                }
                try {
                    const port = portAllocator.allocate();
                    const instance = await startDatabase(dbConfig, port);
                    databases.set(dbName, instance);
                    startedDatabases.push(instance);
                    await stateManager.registerDatabase(dbName, {
                        engine: instance.engine,
                        port: instance.port,
                        host: instance.host,
                        user: instance.user,
                        password: instance.password,
                        dbName: instance.dbName,
                        url: instance.url,
                    });
                }
                catch (err) {
                    console.error(`Error: Failed to start database "${dbName}": ${err instanceof Error ? err.message : String(err)}`);
                    await cleanup();
                    process.exit(1);
                }
            }
        }
    }
    // Prepare secrets and resolve environment variables
    let resolvedEnv;
    try {
        const secrets = await prepareSecrets(config.secrets);
        resolvedEnv = resolveEnvForExec(service.env, databases, secrets);
    }
    catch (err) {
        console.error(`Error: Failed to resolve environment variables: ${err instanceof Error ? err.message : String(err)}`);
        await cleanup();
        process.exit(1);
    }
    // Handle signals - forward to child and cleanup
    let child = null;
    const handleSignal = async (signal) => {
        if (child && !child.killed) {
            child.kill(signal);
        }
        await cleanup();
    };
    process.on("SIGINT", () => handleSignal("SIGINT"));
    process.on("SIGTERM", () => handleSignal("SIGTERM"));
    // Execute command with inherited stdio for full interactivity
    child = spawn(command[0], command.slice(1), {
        cwd: process.cwd(),
        env: {
            ...process.env,
            ...resolvedEnv,
        },
        stdio: "inherit",
    });
    child.on("exit", async (code, signal) => {
        await cleanup();
        if (signal) {
            process.kill(process.pid, signal);
        }
        else {
            process.exit(code ?? 0);
        }
    });
    child.on("error", async (err) => {
        console.error(`Error: Failed to execute command: ${err.message}`);
        await cleanup();
        process.exit(1);
    });
}
//# sourceMappingURL=exec.js.map