"use strict";
/**
 * Start Command
 * Issue #96: npm install CLI support
 * Issue #125: Use getEnvPath and getPidFilePath for correct path resolution
 * Issue #136: Add --issue and --auto-port flags for worktree support
 * Issue #331: Add --auth, --auth-expire, --https, --cert, --key, --allow-http flags
 * Start CommandMate server
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.startCommand = startCommand;
const fs_1 = require("fs");
const child_process_1 = require("child_process");
const dotenv_1 = require("dotenv");
const types_1 = require("../types");
const logger_1 = require("../utils/logger");
const daemon_1 = require("../utils/daemon");
const security_logger_1 = require("../utils/security-logger");
const paths_1 = require("../utils/paths");
const env_setup_1 = require("../utils/env-setup");
const security_messages_1 = require("../config/security-messages");
const input_validators_1 = require("../utils/input-validators");
const port_allocator_1 = require("../utils/port-allocator");
const resource_resolvers_1 = require("../utils/resource-resolvers");
const auth_1 = require("../../lib/security/auth");
const logger = new logger_1.CLILogger();
/**
 * Display the generated authentication token to the user.
 * Called once after server start; the token is not persisted and cannot be retrieved later.
 *
 * @param token - The plaintext authentication token to display
 */
function displayAuthToken(token) {
    logger.info('');
    logger.info('Authentication token (save this - it will not be shown again):');
    logger.info(`  ${token}`);
    logger.info('');
}
/** HTTPS certificate warning when --auth is used without --cert/--key */
const HTTPS_WARNING = `
\x1b[1m\x1b[33mWARNING: Authentication enabled without HTTPS\x1b[0m

Token will be transmitted in plain text over HTTP.
For secure authentication, use HTTPS with a TLS certificate.

\x1b[1mQuick setup with mkcert:\x1b[0m
  brew install mkcert     # macOS
  mkcert -install
  mkcert localhost

  commandmate start --auth --cert ./localhost.pem --key ./localhost-key.pem

Use --allow-http to suppress this warning.
`;
/**
 * Execute start command
 * Issue #125: Use getEnvPath and getPidFilePath for correct path resolution
 * Issue #136: Support --issue and --auto-port flags for worktree servers
 * Issue #331: Support --auth, --auth-expire, --https, --cert, --key, --allow-http
 */
async function startCommand(options) {
    try {
        // Issue #136: Validate issue number if provided
        if (options.issue !== undefined) {
            const validation = (0, input_validators_1.validateIssueNoResult)(options.issue);
            if (!validation.valid) {
                logger.error(`Invalid issue number: ${validation.error}`);
                process.exit(types_1.ExitCode.START_FAILED);
                return;
            }
        }
        // Issue #331: Validate auth-expire format before proceeding
        if (options.authExpire) {
            try {
                (0, auth_1.parseDuration)(options.authExpire);
            }
            catch (error) {
                logger.error(`Invalid --auth-expire value: ${(0, types_1.getErrorMessage)(error)}`);
                process.exit(types_1.ExitCode.CONFIG_ERROR);
                return;
            }
        }
        // Issue #125: Check for .env file at correct location
        // Issue #136: Use issue number for worktree-specific paths
        const envPath = (0, env_setup_1.getEnvPath)(options.issue);
        const pidFilePath = (0, env_setup_1.getPidFilePath)(options.issue);
        // Issue #136: Worktree-specific server label
        const serverLabel = options.issue !== undefined
            ? `Issue #${options.issue} server`
            : 'Main server';
        // Issue #136: For worktree servers, main .env must exist but worktree-specific .env is optional
        const mainEnvPath = (0, env_setup_1.getEnvPath)();
        if (!(0, fs_1.existsSync)(mainEnvPath)) {
            logger.error(`.env file not found at ${mainEnvPath}`);
            logger.info('Run "commandmate init" to create a configuration file');
            process.exit(types_1.ExitCode.CONFIG_ERROR);
            return;
        }
        const daemonManager = new daemon_1.DaemonManager(pidFilePath);
        // Issue #136: Handle auto-port allocation for worktree servers
        let port = options.port;
        if (options.autoPort && options.issue !== undefined) {
            const portAllocator = port_allocator_1.PortAllocator.getInstance();
            port = portAllocator.allocate(options.issue);
            logger.info(`Auto-allocated port ${port} for Issue #${options.issue}`);
        }
        // Issue #136: Set worktree-specific DB path
        let dbPath;
        if (options.issue !== undefined) {
            const dbResolver = new resource_resolvers_1.DbPathResolver();
            dbPath = dbResolver.resolve(options.issue);
            logger.info(`Using database: ${dbPath}`);
        }
        // Issue #331: Generate token and hash if --auth is enabled
        let authTokenHash;
        let authToken;
        if (options.auth) {
            authToken = (0, auth_1.generateToken)();
            authTokenHash = (0, auth_1.hashToken)(authToken);
        }
        // Issue #331: Validate --cert/--key must be specified together
        if ((options.cert && !options.key) || (!options.cert && options.key)) {
            logger.error('--cert and --key must be specified together');
            process.exit(types_1.ExitCode.CONFIG_ERROR);
            return;
        }
        // Issue #331: Determine protocol (matches server.ts: cert+key → always HTTPS)
        const hasCert = !!(options.cert && options.key);
        const protocol = hasCert ? 'https' : 'http';
        // Issue #331: HTTPS certificate validation
        if (hasCert) {
            if (!(0, fs_1.existsSync)(options.cert)) {
                logger.error(`Certificate file not found: ${options.cert}`);
                process.exit(types_1.ExitCode.CONFIG_ERROR);
                return;
            }
            if (!(0, fs_1.existsSync)(options.key)) {
                logger.error(`Key file not found: ${options.key}`);
                process.exit(types_1.ExitCode.CONFIG_ERROR);
                return;
            }
        }
        // Issue #331: Warn about --auth without HTTPS
        if (options.auth && !hasCert && !options.allowHttp) {
            console.log(HTTPS_WARNING);
        }
        // Load .env to check for legacy CM_AUTH_TOKEN
        const mainEnvResult = (0, dotenv_1.config)({ path: mainEnvPath });
        let envResult = mainEnvResult;
        if (options.issue !== undefined && (0, fs_1.existsSync)(envPath) && envPath !== mainEnvPath) {
            envResult = (0, dotenv_1.config)({ path: envPath, override: true });
        }
        // Issue #331: Warn about legacy CM_AUTH_TOKEN
        const allEnvParsed = {
            ...(mainEnvResult.parsed || {}),
            ...(envResult.parsed || {}),
        };
        if (allEnvParsed.CM_AUTH_TOKEN) {
            logger.warn('CM_AUTH_TOKEN found in .env file. This is no longer used.');
            logger.warn('Use "commandmate start --auth" instead. The token is now generated automatically.');
        }
        if (mainEnvResult.error) {
            logger.warn(`Failed to load .env file at ${mainEnvPath}: ${mainEnvResult.error.message}`);
            logger.info('Continuing with existing environment variables');
        }
        // Daemon mode
        if (options.daemon) {
            // Check if already running
            if (await daemonManager.isRunning()) {
                const status = await daemonManager.getStatus();
                logger.error(`${serverLabel} is already running (PID: ${status?.pid})`);
                process.exit(types_1.ExitCode.START_FAILED);
                return;
            }
            logger.info(`Starting ${serverLabel} in background...`);
            // Issue #331: Set auth environment variables in process.env so daemon.ts can forward them
            // to the child process. daemon.ts reads process.env[key] to build the child env object.
            if (authTokenHash) {
                process.env.CM_AUTH_TOKEN_HASH = authTokenHash;
            }
            if (options.authExpire) {
                process.env.CM_AUTH_EXPIRE = options.authExpire;
            }
            if (options.cert) {
                process.env.CM_HTTPS_CERT = options.cert;
            }
            if (options.key) {
                process.env.CM_HTTPS_KEY = options.key;
            }
            if (options.allowHttp) {
                process.env.CM_ALLOW_HTTP = '1';
            }
            // Issue #332: Set IP restriction env vars for daemon mode
            if (options.allowedIps) {
                process.env.CM_ALLOWED_IPS = options.allowedIps;
            }
            if (options.trustProxy) {
                process.env.CM_TRUST_PROXY = 'true';
            }
            try {
                const pid = await daemonManager.start({
                    dev: options.dev,
                    port: port,
                    // Issue #136: Pass DB path for worktree servers
                    dbPath: dbPath,
                    // Issue #331: Pass auth and HTTPS options
                    auth: options.auth,
                    authExpire: options.authExpire,
                    cert: options.cert,
                    key: options.key,
                    allowHttp: options.allowHttp,
                });
                logger.success(`${serverLabel} started in background (PID: ${pid})`);
                const actualPort = port || parseInt(process.env.CM_PORT || '3000', 10);
                const bind = process.env.CM_BIND || '127.0.0.1';
                const url = `${protocol}://${bind === '0.0.0.0' ? '127.0.0.1' : bind}:${actualPort}`;
                logger.info(`URL: ${url}`);
                // Issue #331: Show token to user (shown once, not persisted)
                if (authToken) {
                    displayAuthToken(authToken);
                }
                (0, security_logger_1.logSecurityEvent)({
                    timestamp: new Date().toISOString(),
                    command: 'start',
                    action: 'success',
                    details: `Daemon started (PID: ${pid})${options.issue !== undefined ? ` (Issue #${options.issue})` : ''}${options.auth ? ' [auth enabled]' : ''}`,
                });
                process.exit(types_1.ExitCode.SUCCESS);
            }
            catch (error) {
                const message = (0, types_1.getErrorMessage)(error);
                logger.error(`Failed to start daemon: ${message}`);
                (0, security_logger_1.logSecurityEvent)({
                    timestamp: new Date().toISOString(),
                    command: 'start',
                    action: 'failure',
                    details: message,
                });
                process.exit(types_1.ExitCode.START_FAILED);
            }
            return;
        }
        // Foreground mode (default)
        const npmScript = options.dev ? 'dev' : 'start';
        logger.info(`Starting ${serverLabel} in foreground (${options.dev ? 'development' : 'production'} mode)...`);
        // Build environment by merging process.env with .env values
        const env = {
            ...process.env,
            ...(mainEnvResult.parsed || {}),
            ...(envResult.parsed || {}),
        };
        // Command line options override .env values
        if (port) {
            env.CM_PORT = String(port);
        }
        // Issue #136: Set DB path for worktree servers
        if (dbPath) {
            env.CM_DB_PATH = dbPath;
        }
        // Issue #331: Set auth environment variables
        if (authTokenHash) {
            env.CM_AUTH_TOKEN_HASH = authTokenHash;
        }
        if (options.authExpire) {
            env.CM_AUTH_EXPIRE = options.authExpire;
        }
        if (options.cert) {
            env.CM_HTTPS_CERT = options.cert;
        }
        if (options.key) {
            env.CM_HTTPS_KEY = options.key;
        }
        if (options.allowHttp) {
            env.CM_ALLOW_HTTP = '1';
        }
        // Issue #332: Set IP restriction env vars for foreground mode
        if (options.allowedIps) {
            env.CM_ALLOWED_IPS = options.allowedIps;
        }
        if (options.trustProxy) {
            env.CM_TRUST_PROXY = 'true';
        }
        // Issue #179: Security warning for external access - recommend reverse proxy
        const bindAddress = env.CM_BIND || '127.0.0.1';
        // Issue #332: Suppress warning when --allowed-ips is set (IP restriction provides access control)
        if (bindAddress === '0.0.0.0' && !options.auth && !options.allowedIps) {
            console.log(security_messages_1.REVERSE_PROXY_WARNING);
        }
        // Issue #331: Show token to user (foreground mode, shown once, not persisted)
        if (authToken) {
            displayAuthToken(authToken);
        }
        // Use package installation directory, not current working directory
        const packageRoot = (0, paths_1.getPackageRoot)();
        const child = (0, child_process_1.spawn)('npm', ['run', npmScript], {
            cwd: packageRoot,
            env,
            stdio: 'inherit',
        });
        child.on('error', (err) => {
            logger.error(`Failed to start server: ${err.message}`);
            (0, security_logger_1.logSecurityEvent)({
                timestamp: new Date().toISOString(),
                command: 'start',
                action: 'failure',
                details: err.message,
            });
            process.exit(types_1.ExitCode.START_FAILED);
        });
        child.on('close', (code) => {
            const exitCode = code ?? 0;
            (0, security_logger_1.logSecurityEvent)({
                timestamp: new Date().toISOString(),
                command: 'start',
                action: exitCode === 0 ? 'success' : 'failure',
                details: `Server exited with code ${exitCode}`,
            });
            process.exit(exitCode);
        });
    }
    catch (error) {
        const message = (0, types_1.getErrorMessage)(error);
        logger.error(`Start failed: ${message}`);
        (0, security_logger_1.logSecurityEvent)({
            timestamp: new Date().toISOString(),
            command: 'start',
            action: 'failure',
            details: message,
        });
        process.exit(types_1.ExitCode.UNEXPECTED_ERROR);
    }
}
