/**
 * Bare-Metal Companion Services Launcher
 *
 * Starts the license server and dashboard as background child processes
 * when running in bare-metal mode (no Docker container).
 *
 * Generates the 5 required secrets locally (same as docker-entrypoint.sh
 * Strategy 3) and persists them to ~/.ocr-provenance-mcp/.license_secrets.
 *
 * @module bare-metal-services
 */
import { spawn } from 'child_process';
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
import { join, dirname } from 'path';
import { homedir } from 'os';
import { fileURLToPath } from 'url';
import { randomBytes, generateKeyPairSync } from 'crypto';
const __dirname = dirname(fileURLToPath(import.meta.url));
const BARE_METAL_DIR = join(homedir(), '.ocr-provenance-mcp');
const DASHBOARD_DIR = join(BARE_METAL_DIR, 'dashboard');
const SECRETS_FILE = join(BARE_METAL_DIR, '.license_secrets');
// License server path — check npm package location first, then source repo fallback
const LICENSE_SERVER_DIRS = [
    join(__dirname, '..', 'packages', 'server', 'dist'), // npm package: <pkg>/packages/server/dist/
    join(__dirname, '..', '..', 'packages', 'server', 'dist'), // source repo fallback
];
const activeProcesses = [];
function log(msg) {
    process.stderr.write(`[bare-metal] ${msg}\n`);
}
/**
 * Load or generate the 5 required secrets for the license server.
 * Persists to disk so the same secrets are used across restarts
 * (same key = same balance = no orphaned users).
 */
function ensureSecrets() {
    // Try loading from disk first
    if (existsSync(SECRETS_FILE)) {
        try {
            const raw = readFileSync(SECRETS_FILE, 'utf-8');
            const secrets = JSON.parse(raw);
            if (secrets.SESSION_SECRET && secrets.LICENSE_SIGNING_KEY &&
                secrets.LICENSE_PUBLIC_KEY && secrets.WORKER_AUTH_SECRET &&
                secrets.WEBHOOK_SHARED_SECRET) {
                log('Loaded license secrets from cache');
                return secrets;
            }
        }
        catch {
            // Corrupted file — regenerate
        }
    }
    // Generate fresh secrets (same logic as docker-entrypoint.sh)
    log('Generating local license secrets...');
    const sessionSecret = randomBytes(32).toString('hex');
    const workerAuthSecret = randomBytes(32).toString('hex');
    const webhookSharedSecret = randomBytes(32).toString('hex');
    // Ed25519 keypair — same DER extraction as docker-entrypoint.sh
    const { publicKey, privateKey } = generateKeyPairSync('ed25519');
    const privDer = privateKey.export({ format: 'der', type: 'pkcs8' });
    const pubDer = publicKey.export({ format: 'der', type: 'spki' });
    const signingKey = privDer.subarray(16).toString('hex');
    const publicKeyHex = pubDer.subarray(12).toString('hex');
    const secrets = {
        SESSION_SECRET: sessionSecret,
        LICENSE_SIGNING_KEY: signingKey,
        LICENSE_PUBLIC_KEY: publicKeyHex,
        WORKER_AUTH_SECRET: workerAuthSecret,
        WEBHOOK_SHARED_SECRET: webhookSharedSecret,
    };
    // Persist to disk (chmod 600 for security)
    mkdirSync(BARE_METAL_DIR, { recursive: true });
    writeFileSync(SECRETS_FILE, JSON.stringify(secrets, null, 2), 'utf-8');
    try {
        chmodSync(SECRETS_FILE, 0o600);
    }
    catch { /* Windows doesn't support chmod */ }
    log('License secrets generated and cached');
    return secrets;
}
// ---------------------------------------------------------------------------
// Service Startup
// ---------------------------------------------------------------------------
/**
 * Start companion services for bare-metal mode.
 * Non-blocking — services start in the background.
 * Failures are logged but do not prevent MCP server startup.
 */
export function startBareMetalServices() {
    // Clean up child processes on exit
    const cleanup = () => {
        for (const proc of activeProcesses) {
            try {
                proc.kill();
            }
            catch { /* already dead */ }
        }
    };
    process.on('exit', cleanup);
    process.on('SIGTERM', () => { cleanup(); process.exit(0); });
    process.on('SIGINT', () => { cleanup(); process.exit(0); });
    // Generate/load secrets BEFORE starting any service
    const secrets = ensureSecrets();
    // Export secrets to process.env so they're available to child processes
    // and to the MCP server itself (for license client auth)
    process.env.SESSION_SECRET = secrets.SESSION_SECRET;
    process.env.LICENSE_SIGNING_KEY = secrets.LICENSE_SIGNING_KEY;
    process.env.LICENSE_PUBLIC_KEY = secrets.LICENSE_PUBLIC_KEY;
    process.env.WORKER_AUTH_SECRET = secrets.WORKER_AUTH_SECRET;
    process.env.WEBHOOK_SHARED_SECRET = secrets.WEBHOOK_SHARED_SECRET;
    process.env.CHECKOUT_API_URL = process.env.CHECKOUT_API_URL ?? 'https://api.ocrprovenance.com';
    process.env.DASHBOARD_URL = process.env.DASHBOARD_URL ?? 'http://127.0.0.1:3367';
    startLicenseServer(secrets);
    startDashboard(secrets);
}
function startLicenseServer(secrets) {
    let entry = null;
    for (const dir of LICENSE_SERVER_DIRS) {
        const candidate = join(dir, 'index.js');
        if (existsSync(candidate)) {
            entry = candidate;
            break;
        }
    }
    if (!entry) {
        log('License server not found — billing features unavailable.');
        log('  OCR processing works without billing. To add funds, use Docker mode.');
        return;
    }
    const port = process.env.LICENSE_PORT ?? '3000';
    log(`Starting license server on port ${port}...`);
    const proc = spawn('node', [entry], {
        env: {
            ...process.env,
            // License server required env vars
            LICENSE_PORT: port,
            SESSION_SECRET: secrets.SESSION_SECRET,
            LICENSE_SIGNING_KEY: secrets.LICENSE_SIGNING_KEY,
            LICENSE_PUBLIC_KEY: secrets.LICENSE_PUBLIC_KEY,
            WORKER_AUTH_SECRET: secrets.WORKER_AUTH_SECRET,
            WEBHOOK_SHARED_SECRET: secrets.WEBHOOK_SHARED_SECRET,
            CHECKOUT_API_URL: process.env.CHECKOUT_API_URL ?? 'https://api.ocrprovenance.com',
            DASHBOARD_URL: process.env.DASHBOARD_URL ?? `http://127.0.0.1:3367`,
            // Prevent PORT from leaking (PORT is for dashboard/Next.js)
            PORT: undefined,
        },
        stdio: ['pipe', 'pipe', 'pipe'],
        detached: false,
    });
    activeProcesses.push(proc);
    proc.stderr?.on('data', (chunk) => {
        for (const line of chunk.toString().split('\n')) {
            if (line.trim())
                process.stderr.write(`[license] ${line}\n`);
        }
    });
    proc.on('exit', (code) => {
        if (code !== 0 && code !== null) {
            log(`License server exited with code ${code}`);
        }
    });
}
function startDashboard(secrets) {
    let dashboardDir = DASHBOARD_DIR;
    let dashboardServer = join(DASHBOARD_DIR, 'server.js');
    if (!existsSync(dashboardServer)) {
        // Fallback: check bundled dashboard in npm package (before install --mac extraction)
        const bundled = join(__dirname, '..', 'packages', 'wrapper', 'dashboard-bundle', 'server.js');
        if (existsSync(bundled)) {
            dashboardDir = join(__dirname, '..', 'packages', 'wrapper', 'dashboard-bundle');
            dashboardServer = bundled;
        }
        else {
            log('Dashboard not installed — web UI unavailable.');
            log('  Re-run: npx -y ocr-provenance-mcp install --mac');
            return;
        }
    }
    const port = process.env.DASHBOARD_PORT ?? '3367';
    const licensePort = process.env.LICENSE_PORT ?? '3000';
    const mcpPort = process.env.MCP_HTTP_PORT ?? '3366';
    log(`Starting dashboard on port ${port}...`);
    const proc = spawn('node', [dashboardServer], {
        cwd: dashboardDir,
        env: {
            PATH: process.env.PATH,
            HOME: dashboardDir,
            NODE_ENV: 'production',
            PORT: port,
            HOSTNAME: '127.0.0.1',
            SESSION_SECRET: secrets.SESSION_SECRET,
            OCR_LICENSE_KEY: process.env.OCR_LICENSE_KEY ?? '',
            OCR_LICENSE_SERVER: `http://127.0.0.1:${licensePort}`,
            MCP_SERVER_URL: `http://127.0.0.1:${mcpPort}`,
            DASHBOARD_URL: `http://127.0.0.1:${port}`,
        },
        stdio: ['pipe', 'pipe', 'pipe'],
        detached: false,
    });
    activeProcesses.push(proc);
    proc.stderr?.on('data', (chunk) => {
        for (const line of chunk.toString().split('\n')) {
            if (line.trim())
                process.stderr.write(`[dashboard] ${line}\n`);
        }
    });
    proc.stdout?.on('data', (chunk) => {
        const text = chunk.toString();
        if (text.includes('Ready')) {
            log(`Dashboard ready at http://localhost:${port}`);
        }
    });
    proc.on('exit', (code) => {
        if (code !== 0 && code !== null) {
            log(`Dashboard exited with code ${code}`);
        }
    });
}
//# sourceMappingURL=bare-metal-services.js.map