/**
 * Home23 CLI — Ecosystem config generator
 *
 * Scans all agent instances, reads ports and agent names,
 * generates ecosystem.config.cjs with 4 PM2 entries per agent.
 */

import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from 'node:fs';
import { join } from 'node:path';
import yaml from 'js-yaml';

function loadYaml(filePath) {
  if (!existsSync(filePath)) return {};
  return yaml.load(readFileSync(filePath, 'utf8')) || {};
}

function discoverAgents(home23Root) {
  const instancesDir = join(home23Root, 'instances');
  if (!existsSync(instancesDir)) return [];

  return readdirSync(instancesDir)
    .filter((name) => {
      const dir = join(instancesDir, name);
      return statSync(dir).isDirectory() && existsSync(join(dir, 'config.yaml'));
    })
    .map((name) => {
      const config = loadYaml(join(instancesDir, name, 'config.yaml'));
      return { name, config };
    });
}

function resolvePrimaryAgent(agents, homeConfig) {
  const configured = String(homeConfig?.home?.primaryAgent || '').trim();
  if (configured && agents.some(agent => agent.name === configured)) {
    return configured;
  }

  const ranked = [...agents].sort((a, b) => {
    const dashA = Number(a.config?.ports?.dashboard) || Number.MAX_SAFE_INTEGER;
    const dashB = Number(b.config?.ports?.dashboard) || Number.MAX_SAFE_INTEGER;
    const engineA = Number(a.config?.ports?.engine) || Number.MAX_SAFE_INTEGER;
    const engineB = Number(b.config?.ports?.engine) || Number.MAX_SAFE_INTEGER;
    return dashA - dashB || engineA - engineB || a.name.localeCompare(b.name);
  });
  return ranked[0]?.name || null;
}

export function generateEcosystem(home23Root) {
  const agents = discoverAgents(home23Root);

  if (agents.length === 0) {
    console.log('  No agents found in instances/ — skipping ecosystem generation');
    return;
  }

  const homeConfig = loadYaml(join(home23Root, 'config', 'home.yaml'));
  const primaryAgent = resolvePrimaryAgent(agents, homeConfig);
  const orderedAgents = [...agents].sort((a, b) => {
    const aPrimary = a.name === primaryAgent ? 1 : 0;
    const bPrimary = b.name === primaryAgent ? 1 : 0;
    const dashA = Number(a.config?.ports?.dashboard) || Number.MAX_SAFE_INTEGER;
    const dashB = Number(b.config?.ports?.dashboard) || Number.MAX_SAFE_INTEGER;
    return bPrimary - aPrimary || dashA - dashB || a.name.localeCompare(b.name);
  });

  const lines = [];
  lines.push(`// Home23 — PM2 Process Configuration (auto-generated)`);
  lines.push(`// Do not edit manually — regenerated by: node cli/home23.js agent create`);
  lines.push(`//`);
  lines.push(`// Agents: ${orderedAgents.map((a) => a.name).join(', ')}`);
  lines.push(``);
  lines.push(`const fs = require('fs');`);
  lines.push(`const path = require('path');`);
  lines.push(`const yaml = require('js-yaml');`);
  lines.push(``);
  lines.push(`const HOME23 = __dirname;`);
  lines.push(`const ENGINE = path.join(HOME23, 'engine');`);
  lines.push(`const ENGINE_KILL_TIMEOUT_MS = 210000;`);
  lines.push(`const PM2_INHERITANCE_BLOCKLIST = ['cron_restart', 'watch', 'HOME23_AGENT', 'INSTANCE_ID', 'DASHBOARD_PORT', 'COSMO_DASHBOARD_PORT', 'REALTIME_PORT', 'MCP_HTTP_PORT', 'COSMO_RUNTIME_DIR', 'COSMO_WORKSPACE_PATH'];`);
  lines.push(`for (const key of PM2_INHERITANCE_BLOCKLIST) delete process.env[key];`);
  lines.push(``);
  lines.push(`function loadYaml(filePath) {`);
  lines.push(`  if (!fs.existsSync(filePath)) return {};`);
  lines.push(`  return yaml.load(fs.readFileSync(filePath, 'utf8')) || {};`);
  lines.push(`}`);
  lines.push(``);
  lines.push(`const homeConfig = loadYaml(path.join(HOME23, 'config', 'home.yaml'));`);
  lines.push(`const secrets = loadYaml(path.join(HOME23, 'config', 'secrets.yaml'));`);
  lines.push(`const ollamaLocalUrl = homeConfig.providers?.['ollama-local']?.baseUrl || 'http://127.0.0.1:11434';`);
  lines.push(`const screenlogicConfig = homeConfig.screenlogic || {};`);
  lines.push(`const screenlogicVenvPython = path.join(HOME23, 'runtime', 'screenlogic-venv', 'bin', 'python');`);
  lines.push(`const screenlogicPython = screenlogicConfig.python || (fs.existsSync(screenlogicVenvPython) ? screenlogicVenvPython : 'python3');`);
  lines.push(``);
  lines.push(`// Cosmo23 OAuth encryption key — read from secrets.yaml (init generates it)`);
  lines.push(`const cosmo23EncryptionKey = secrets.cosmo23?.encryptionKey || '';`);
  lines.push(`if (!cosmo23EncryptionKey) {`);
  lines.push(`  console.warn('[ecosystem] Warning: cosmo23 encryption key not found in secrets.yaml. Run "home23 init" to generate.');`);
  lines.push(`}`);
  lines.push(`const cosmo23DbUrl = 'file:' + path.join(HOME23, 'cosmo23', '.cosmo23-config', 'database.db');`);
  lines.push(``);
  lines.push(`const commonEnv = {`);
  lines.push(`  NODE_ENV: 'production',`);
  lines.push(`  COSMO_CONFIG_PATH: path.join(HOME23, 'configs', 'base-engine.yaml'),`);
  lines.push(`  EMBEDDING_BASE_URL: \`\${ollamaLocalUrl}/v1\`,`);
  lines.push(`  LOCAL_LLM_BASE_URL: \`\${ollamaLocalUrl}/v1\`,`);
  lines.push(`  OLLAMA_CLOUD_API_KEY: secrets.providers?.['ollama-cloud']?.apiKey || '',`);
  lines.push(`  MINIMAX_API_KEY: secrets.providers?.minimax?.apiKey || '',`);
  lines.push(`  ANTHROPIC_AUTH_TOKEN: secrets.providers?.anthropic?.apiKey || '',`);
  lines.push(`  OPENAI_CODEX_AUTH_TOKEN: secrets.providers?.['openai-codex']?.apiKey || '',`);
  lines.push(`  OPENAI_API_KEY: secrets.providers?.openai?.apiKey || '',`);
  lines.push(`  XAI_API_KEY: secrets.providers?.xai?.apiKey || '',`);
  lines.push(`};`);
  lines.push(``);
  lines.push(`module.exports = {`);
  lines.push(`  apps: [`);

  for (const agent of orderedAgents) {
    const ports = agent.config.ports || {};
    const wsPort = ports.engine || 5001;
    const dashPort = ports.dashboard || 5002;
    const mcpPort = ports.mcp || 5003;
    const brainDir = `path.join(HOME23, 'instances', '${agent.name}', 'brain')`;
    const workspaceDir = `path.join(HOME23, 'instances', '${agent.name}', 'workspace')`;
    const logsDir = `path.join(HOME23, 'instances', '${agent.name}', 'logs')`;

    lines.push(``);
    lines.push(`    // ── ${agent.name} ──`);

    // Engine
    lines.push(`    {`);
    lines.push(`      name: 'home23-${agent.name}',`);
    lines.push(`      script: 'src/index.js',`);
    lines.push(`      cwd: ENGINE,`);
    lines.push(`      filter_env: ['HOME23_AGENT', 'INSTANCE_ID', 'DASHBOARD_PORT', 'COSMO_DASHBOARD_PORT', 'REALTIME_PORT', 'MCP_HTTP_PORT', 'COSMO_RUNTIME_DIR', 'COSMO_WORKSPACE_PATH'],`);
    // Heap sized for a cognitive engine with a growing brain — see commit
    // 174c76c (Step: engine OOM fix). 768MB caused a restart loop at ~7k nodes.
    lines.push(`      node_args: '--expose-gc --max-old-space-size=4096',`);
    lines.push(`      max_memory_restart: '5G',`);
    lines.push(`      kill_timeout: ENGINE_KILL_TIMEOUT_MS,`);
    lines.push(`      autorestart: true, watch: false, merge_logs: true,`);
    lines.push(`      out_file: ${logsDir} + '/engine-out.log',`);
    lines.push(`      error_file: ${logsDir} + '/engine-err.log',`);
    lines.push(`      env: { ...commonEnv, HOME23_AGENT: '${agent.name}', COSMO_RUNTIME_DIR: ${brainDir}, COSMO_WORKSPACE_PATH: ${workspaceDir}, DASHBOARD_PORT: '${dashPort}', COSMO_DASHBOARD_PORT: '${dashPort}', REALTIME_PORT: '${wsPort}', MCP_HTTP_PORT: '${mcpPort}', INSTANCE_ID: 'home23-${agent.name}' },`);
    lines.push(`    },`);

    // Dashboard
    lines.push(`    {`);
    lines.push(`      name: 'home23-${agent.name}-dash',`);
    lines.push(`      script: 'src/dashboard/server.js',`);
    lines.push(`      cwd: ENGINE,`);
    lines.push(`      filter_env: ['HOME23_AGENT', 'INSTANCE_ID', 'DASHBOARD_PORT', 'COSMO_DASHBOARD_PORT', 'REALTIME_PORT', 'MCP_HTTP_PORT', 'COSMO_RUNTIME_DIR', 'COSMO_WORKSPACE_PATH'],`);
    lines.push(`      node_args: '--max-old-space-size=2048',`);
    lines.push(`      max_memory_restart: '3G',`);
    lines.push(`      autorestart: true, watch: false, merge_logs: true,`);
    lines.push(`      out_file: ${logsDir} + '/dashboard-out.log',`);
    lines.push(`      error_file: ${logsDir} + '/dashboard-err.log',`);
    lines.push(`      env: { ...commonEnv, HOME23_AGENT: '${agent.name}', COSMO_RUNTIME_DIR: ${brainDir}, COSMO_WORKSPACE_PATH: ${workspaceDir}, DASHBOARD_PORT: '${dashPort}', COSMO_DASHBOARD_PORT: '${dashPort}', REALTIME_PORT: '${wsPort}', MCP_HTTP_PORT: '${mcpPort}', INSTANCE_ID: 'home23-${agent.name}' },`);
    lines.push(`    },`);

    // Harness
    lines.push(`    {`);
    lines.push(`      name: 'home23-${agent.name}-harness',`);
    lines.push(`      script: 'dist/home.js',`);
    lines.push(`      cwd: HOME23,`);
    lines.push(`      filter_env: ['cron_restart', 'HOME23_AGENT', 'INSTANCE_ID', 'DASHBOARD_PORT', 'COSMO_DASHBOARD_PORT', 'REALTIME_PORT', 'MCP_HTTP_PORT', 'COSMO_RUNTIME_DIR', 'COSMO_WORKSPACE_PATH'],`);
    // Heap raised from 1.5GB default to 4GB. Long-running agent sessions
    // with many LLM calls + tool-use chains can accumulate response blobs
    // in closure scope faster than GC reclaims. max_memory_restart acts
    // as a safety net — if we ever do leak past 5GB, PM2 restarts instead
    // of staying dead.
    // --cpu-prof + --heap-prof write .cpuprofile and .heapprofile to logs/ on
    // graceful exit. cpu-prof shows JS main-thread time; heap-prof shows
    // allocation hot paths — needed because the harness CPU climb manifests as
    // V8 background-GC churn, invisible to cpu-prof.
    lines.push(`      node_args: '--expose-gc --max-old-space-size=4096 --cpu-prof --cpu-prof-dir=' + ${logsDir} + ' --cpu-prof-interval=1000 --heap-prof --heap-prof-dir=' + ${logsDir},`);
    lines.push(`      max_memory_restart: '5G',`);
    lines.push(`      autorestart: true, watch: false, merge_logs: true,`);
    lines.push(`      out_file: ${logsDir} + '/harness-out.log',`);
    lines.push(`      error_file: ${logsDir} + '/harness-err.log',`);
    lines.push(`      env: { HOME23_AGENT: '${agent.name}', OLLAMA_CLOUD_API_KEY: commonEnv.OLLAMA_CLOUD_API_KEY, MINIMAX_API_KEY: commonEnv.MINIMAX_API_KEY, ANTHROPIC_AUTH_TOKEN: commonEnv.ANTHROPIC_AUTH_TOKEN, OPENAI_CODEX_AUTH_TOKEN: commonEnv.OPENAI_CODEX_AUTH_TOKEN, OPENAI_API_KEY: commonEnv.OPENAI_API_KEY, XAI_API_KEY: commonEnv.XAI_API_KEY },`);
    lines.push(`    },`);
  }

  // PM2 watchdog — shared supervisor for agent engine/dashboard/harness triplets.
  // This intentionally lives outside the agent harnesses so it can repair a
  // missing engine or harness instead of depending on the failed process.
  lines.push(``);
  lines.push(`    // ── pm2 watchdog (shared) ──`);
  lines.push(`    {`);
  lines.push(`      name: 'home23-watchdog',`);
  lines.push(`      script: path.join(HOME23, 'scripts', 'home23-pm2-watchdog-daemon.cjs'),`);
  lines.push(`      cwd: HOME23,`);
  lines.push(`      filter_env: ['cron_restart', 'watch', 'HOME23_AGENT', 'INSTANCE_ID', 'DASHBOARD_PORT', 'COSMO_DASHBOARD_PORT', 'REALTIME_PORT', 'MCP_HTTP_PORT', 'COSMO_RUNTIME_DIR', 'COSMO_WORKSPACE_PATH'],`);
  // PM2 merges daemon/app env into new starts and can leak a harness
  // cron_restart onto shared services. An impossible schedule wins over that
  // inherited value while behaving as "no cron restart" for the watchdog.
  lines.push(`      cron_restart: '0 0 31 2 *',`);
  lines.push(`      autorestart: true, watch: false, merge_logs: true,`);
  lines.push(`      out_file: path.join(HOME23, 'logs', 'pm2-watchdog-out.log'),`);
  lines.push(`      error_file: path.join(HOME23, 'logs', 'pm2-watchdog-err.log'),`);
  lines.push(`      env: { HOME23_WATCHDOG_INTERVAL_MS: '60000' },`);
  lines.push(`    },`);

  // Chrome CDP — shared headless browser for web_browse tool
  lines.push(``);
  lines.push(`    // ── chrome-cdp (shared) ──`);
  lines.push(`    {`);
  lines.push(`      name: 'home23-chrome-cdp',`);
  lines.push(`      script: path.join(HOME23, 'scripts', 'chrome-cdp.sh'),`);
  lines.push(`      interpreter: 'none',`);
  lines.push(`      filter_env: ['cron_restart', 'HOME23_AGENT', 'INSTANCE_ID', 'DASHBOARD_PORT', 'COSMO_DASHBOARD_PORT', 'REALTIME_PORT', 'MCP_HTTP_PORT', 'COSMO_RUNTIME_DIR', 'COSMO_WORKSPACE_PATH'],`);
  lines.push(`      autorestart: true, watch: false, merge_logs: true,`);
  lines.push(`      out_file: path.join(HOME23, 'logs', 'chrome-cdp-out.log'),`);
  lines.push(`      error_file: path.join(HOME23, 'logs', 'chrome-cdp-err.log'),`);
  lines.push(`      env: { CDP_PORT: '9222' },`);
  lines.push(`    },`);

  // Evobrew — shared process (one per installation)
  lines.push(``);
  lines.push(`    // ── evobrew (shared) ──`);
  lines.push(`    {`);
  lines.push(`      name: 'home23-evobrew',`);
  lines.push(`      script: 'server/server.js',`);
  lines.push(`      cwd: path.join(HOME23, 'evobrew'),`);
  lines.push(`      autorestart: true, watch: false, merge_logs: true,`);
  lines.push(`      out_file: path.join(HOME23, 'logs', 'evobrew-out.log'),`);
  lines.push(`      error_file: path.join(HOME23, 'logs', 'evobrew-err.log'),`);
  lines.push(`      env: {`);
  lines.push(`        ...commonEnv,`);
  lines.push(`        ANTHROPIC_API_KEY: secrets.providers?.anthropic?.apiKey || commonEnv.ANTHROPIC_AUTH_TOKEN || '',`);
  lines.push(`        COSMO_ADMIN_MODE: 'true',`);
  lines.push(`        PORT: String(homeConfig.evobrew?.port || 3405),`);
  lines.push(`        EVOBREW_CONFIG_DIR: path.join(HOME23, 'evobrew'),`);
  lines.push(`        HOME23_MANAGED: 'true',`);
  lines.push(`        NODE_ENV: 'production',`);
  lines.push(`      },`);
  lines.push(`    },`);

  // ScreenLogic — shared personal house integration
  lines.push(``);
  lines.push(`    // ── screenlogic (shared personal tile bridge) ──`);
  lines.push(`    {`);
  lines.push(`      name: 'home23-screenlogic',`);
  lines.push(`      script: path.join(HOME23, 'scripts', 'screenlogic_bridge.py'),`);
  lines.push(`      interpreter: screenlogicPython,`);
  lines.push(`      cwd: HOME23,`);
  lines.push(`      autorestart: true, watch: false, merge_logs: true,`);
  lines.push(`      out_file: path.join(HOME23, 'logs', 'screenlogic-out.log'),`);
  lines.push(`      error_file: path.join(HOME23, 'logs', 'screenlogic-err.log'),`);
  lines.push(`      env: {`);
  lines.push(`        SCREENLOGIC_ENABLED: String(screenlogicConfig.enabled !== false),`);
  lines.push(`        SCREENLOGIC_HOST: String(screenlogicConfig.host || ''),`);
  lines.push(`        SCREENLOGIC_ADAPTER_PORT: String(screenlogicConfig.adapterPort || 80),`);
  lines.push(`        SCREENLOGIC_BRIDGE_HOST: String(screenlogicConfig.bridgeHost || '127.0.0.1'),`);
  lines.push(`        SCREENLOGIC_BRIDGE_PORT: String(screenlogicConfig.bridgePort || 5023),`);
  lines.push(`        SCREENLOGIC_POLL_SECONDS: String(screenlogicConfig.pollSeconds || 60),`);
  lines.push(`        SCREENLOGIC_DISCOVERY: String(screenlogicConfig.discovery !== false),`);
  lines.push(`      },`);
  lines.push(`    },`);

  // Cosmo23 — shared process (one per installation)
  // Derive the primary agent's dashboard port for the Home23 Settings link
  const primaryDashPort = orderedAgents.find(agent => agent.name === primaryAgent)?.config.ports?.dashboard
    || orderedAgents[0]?.config.ports?.dashboard
    || 5002;
  lines.push(``);
  lines.push(`    // ── cosmo23 (shared) ──`);
  lines.push(`    {`);
  lines.push(`      name: 'home23-cosmo23',`);
  lines.push(`      script: 'server/index.js',`);
  lines.push(`      cwd: path.join(HOME23, 'cosmo23'),`);
  lines.push(`      autorestart: true, watch: false, merge_logs: true,`);
  lines.push(`      out_file: path.join(HOME23, 'logs', 'cosmo23-out.log'),`);
  lines.push(`      error_file: path.join(HOME23, 'logs', 'cosmo23-err.log'),`);
  lines.push(`      env: {`);
  lines.push(`        ...commonEnv,`);
  lines.push(`        COSMO23_PORT: String(homeConfig.cosmo23?.ports?.app || 43210),`);
  lines.push(`        COSMO23_WS_PORT: String(homeConfig.cosmo23?.ports?.websocket || 43240),`);
  lines.push(`        COSMO23_DASHBOARD_PORT: String(homeConfig.cosmo23?.ports?.dashboard || 43244),`);
  lines.push(`        COSMO23_MCP_HTTP_PORT: String(homeConfig.cosmo23?.ports?.mcp || 43247),`);
  lines.push(`        COSMO23_CONFIG_DIR: path.join(HOME23, 'cosmo23', '.cosmo23-config'),`);
  lines.push(`        DATABASE_URL: cosmo23DbUrl,`);
  lines.push(`        ENCRYPTION_KEY: cosmo23EncryptionKey,`);
  lines.push(`        COSMO_RUNTIME_DIR: path.join(HOME23, 'cosmo23', 'runs'),`);
  lines.push(`        COSMO_REFERENCE_RUNS_PATHS: homeConfig.cosmo23?.source ? homeConfig.cosmo23.source + '/runs' : '',`);
  lines.push(`        HOME23_MANAGED: 'true',`);
  lines.push(`        HOME23_DASHBOARD_PORT: '${primaryDashPort}',`);
  lines.push(`        NODE_ENV: 'production',`);
  lines.push(`      },`);
  lines.push(`    },`);

  lines.push(`  ],`);
  lines.push(`};`);
  lines.push(``);

  const outputPath = join(home23Root, 'ecosystem.config.cjs');
  writeFileSync(outputPath, lines.join('\n'), 'utf8');
  console.log(`  Regenerated ecosystem.config.cjs (${orderedAgents.length} agent(s))`);

  // Also generate agents manifest for the dashboard UI
  const manifest = orderedAgents.map(a => ({
    name: a.name,
    displayName: a.config.agent?.displayName || a.name,
    dashboardPort: a.config.ports?.dashboard || 5002,
    enginePort: a.config.ports?.engine || 5001,
    isPrimary: a.name === primaryAgent,
  }));
  const manifestPath = join(home23Root, 'config', 'agents.json');
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
  console.log(`  Regenerated config/agents.json`);
}
