"use strict";
/**
 * Daemon Process Manager
 * Issue #96: npm install CLI support
 * Issue #125: Added .env loading and security warnings
 * SF-1: SRP - Process management only (PID handling delegated to PidManager)
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DaemonManager = void 0;
const child_process_1 = require("child_process");
const dotenv_1 = require("dotenv");
const pid_manager_1 = require("./pid-manager");
const paths_1 = require("./paths");
const env_setup_1 = require("./env-setup");
const security_messages_1 = require("../config/security-messages");
const logger_1 = require("./logger");
/**
 * Daemon manager for background server process
 */
class DaemonManager {
    pidManager;
    logger;
    constructor(pidFilePath) {
        this.pidManager = new pid_manager_1.PidManager(pidFilePath);
        this.logger = new logger_1.CLILogger();
    }
    /**
     * Start daemon process
     * Issue #125: Load .env file and add security warnings
     * @returns Process ID of the started daemon
     * @throws Error if already running
     */
    async start(options) {
        if (this.pidManager.isProcessRunning()) {
            const pid = this.pidManager.readPid();
            throw new Error(`Server is already running (PID: ${pid})`);
        }
        // Clean up stale PID file
        this.pidManager.removePid();
        const npmScript = options.dev ? 'dev' : 'start';
        // Use package installation directory, not current working directory
        const packageRoot = (0, paths_1.getPackageRoot)();
        // Issue #125: Load .env file from correct location
        const envPath = (0, env_setup_1.getEnvPath)();
        const envResult = (0, dotenv_1.config)({ path: envPath });
        // Handle .env loading errors with fallback (Stage 2 review: MF-2)
        if (envResult.error) {
            this.logger.warn(`Failed to load .env file at ${envPath}: ${envResult.error.message}`);
            this.logger.info('Continuing with existing environment variables');
        }
        // Build environment by merging process.env with .env values
        const env = {
            ...process.env,
            ...(envResult.parsed || {}),
        };
        // SF-003: Remove CLAUDECODE from env object to prevent nested session detection
        // Uses env object (not process.env) to avoid global side effects (DIP)
        delete env.CLAUDECODE;
        // Command line options override .env values
        if (options.port) {
            env.CM_PORT = String(options.port);
        }
        // Issue #136: Set DB path for worktree servers
        if (options.dbPath) {
            env.CM_DB_PATH = options.dbPath;
        }
        // Issue #331: Forward auth and HTTPS environment variables from parent process to daemon.
        // These are set by startCommand before calling daemon.start().
        // Issue #332: Added CM_ALLOWED_IPS, CM_TRUST_PROXY for IP restriction in daemon mode
        const authEnvKeys = ['CM_AUTH_TOKEN_HASH', 'CM_AUTH_EXPIRE', 'CM_HTTPS_CERT', 'CM_HTTPS_KEY', 'CM_ALLOW_HTTP', 'CM_ALLOWED_IPS', 'CM_TRUST_PROXY'];
        for (const key of authEnvKeys) {
            if (process.env[key]) {
                env[key] = process.env[key];
            }
        }
        // Issue #179: Security warning for external access - recommend reverse proxy
        const bindAddress = env.CM_BIND || '127.0.0.1';
        const port = env.CM_PORT || '3000';
        const protocol = env.CM_HTTPS_CERT ? 'https' : 'http';
        // Issue #332: Suppress warning when CM_ALLOWED_IPS is set (IP restriction provides access control)
        if (bindAddress === '0.0.0.0' && !env.CM_AUTH_TOKEN_HASH && !env.CM_ALLOWED_IPS) {
            console.log(security_messages_1.REVERSE_PROXY_WARNING);
        }
        // Log startup with accurate settings (Stage 4 review: MF-2)
        this.logger.info(`Starting server at ${protocol}://${bindAddress}:${port}`);
        // Spawn detached process
        const child = (0, child_process_1.spawn)('npm', ['run', npmScript], {
            cwd: packageRoot,
            env,
            detached: true,
            stdio: 'ignore',
        });
        // Unref to allow parent to exit
        child.unref();
        const pid = child.pid;
        // Write PID file atomically
        if (!this.pidManager.writePid(pid)) {
            // Failed to write PID - another process may have started
            try {
                process.kill(pid, 'SIGTERM');
            }
            catch {
                // Ignore kill errors
            }
            throw new Error('Failed to write PID file - server may already be running');
        }
        return pid;
    }
    /**
     * Stop daemon process
     * @param force Use SIGKILL instead of SIGTERM
     * @returns true if stopped successfully, false if not running
     */
    async stop(force = false) {
        const pid = this.pidManager.readPid();
        if (pid === null) {
            return false;
        }
        if (!this.pidManager.isProcessRunning()) {
            // Process not running - clean up stale PID file
            this.pidManager.removePid();
            return true;
        }
        const signal = force ? 'SIGKILL' : 'SIGTERM';
        try {
            process.kill(pid, signal);
            // Wait for process to exit
            await this.waitForExit(pid, 10000);
            // Clean up PID file
            this.pidManager.removePid();
            return true;
        }
        catch {
            return false;
        }
    }
    /**
     * Get daemon status
     * @returns Status object or null if no PID file
     */
    async getStatus() {
        const pid = this.pidManager.readPid();
        if (pid === null) {
            return null;
        }
        const running = this.pidManager.isProcessRunning();
        if (!running) {
            return { running: false };
        }
        // Get port from environment or default
        const port = parseInt(process.env.CM_PORT || '3000', 10);
        const bind = process.env.CM_BIND || '127.0.0.1';
        // Issue #331: Use HTTPS protocol if certificate is configured
        const protocol = process.env.CM_HTTPS_CERT ? 'https' : 'http';
        const url = `${protocol}://${bind === '0.0.0.0' ? '127.0.0.1' : bind}:${port}`;
        // Note: Getting accurate uptime would require storing start time
        // For now, we don't track uptime
        return {
            running: true,
            pid,
            port,
            url,
        };
    }
    /**
     * Check if daemon is running
     */
    async isRunning() {
        return this.pidManager.isProcessRunning();
    }
    /**
     * Wait for process to exit
     */
    async waitForExit(pid, timeout) {
        const startTime = Date.now();
        const checkInterval = 100;
        while (Date.now() - startTime < timeout) {
            try {
                process.kill(pid, 0);
                // Process still exists, wait
                await new Promise(resolve => setTimeout(resolve, checkInterval));
            }
            catch {
                // Process exited
                return;
            }
        }
        // Timeout - process may still be running
    }
}
exports.DaemonManager = DaemonManager;
