/**
 * Server-side initialization — called once on first API request per worker.
 * When FORGE_EXTERNAL_SERVICES=1 (set by forge-server), telegram/terminal/tunnel
 * are managed externally — only task runner starts here.
 */

import { ensureRunnerStarted } from './task-manager';
import { startTelegramBot, stopTelegramBot } from './telegram-bot';
import { startWatcherLoop } from './session-watcher';
import { getAdminPassword } from './password';
import { loadSettings, saveSettings } from './settings';
import { startTunnel } from './cloudflared';
import { isEncrypted, SECRET_FIELDS } from './crypto';
import { spawn } from 'node:child_process';
import { join } from 'node:path';

const initKey = Symbol.for('mw-initialized');
const gInit = globalThis as any;

/** Migrate plaintext secrets to encrypted on first run */
function migrateSecrets() {
  try {
    const { existsSync, readFileSync } = require('node:fs');
    const YAML = require('yaml');
    const { getDataDir: _gdd } = require('./dirs');
    const dataDir = _gdd();
    const file = join(dataDir, 'settings.yaml');
    if (!existsSync(file)) return;
    const raw = YAML.parse(readFileSync(file, 'utf-8')) || {};
    let needsSave = false;
    for (const field of SECRET_FIELDS) {
      if (raw[field] && typeof raw[field] === 'string' && !isEncrypted(raw[field])) {
        needsSave = true;
        break;
      }
    }
    if (needsSave) {
      // loadSettings returns decrypted, saveSettings encrypts
      const settings = loadSettings();
      saveSettings(settings);
      console.log('[init] Migrated plaintext secrets to encrypted storage');
    }
  } catch (e) {
    console.error('[init] Secret migration error:', e);
  }
}

/** Auto-detect agent binaries */
function autoDetectAgents() {
  try {
    const settings = loadSettings();
    // Backward compat: detect claude if not configured
    if (!settings.claudePath) {
      const { execSync } = require('node:child_process');
      try {
        const path = execSync('which claude', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
        if (path) {
          settings.claudePath = path;
          saveSettings(settings);
          console.log(`[init] Auto-detected claude: ${path}`);
        }
      } catch {}
    }
    // Detect all agents
    const { autoDetectAgents: detect } = require('./agents');
    detect();
  } catch {}
}

export function ensureInitialized() {
  if (gInit[initKey]) return;
  gInit[initKey] = true;

  // Per-step timing — earlier startup hangs were invisible because all
  // these steps log heterogeneously (or not at all). \`time(label, fn)\`
  // prints \`[init] <label> took 1234ms\` for anything taking >250ms so
  // we can spot the slow one without instrumenting elsewhere.
  const time = <T>(label: string, fn: () => T): T => {
    const t = Date.now();
    try { return fn(); }
    finally {
      const ms = Date.now() - t;
      if (ms >= 250) console.log(`[init] ${label} took ${ms}ms`);
    }
  };

  time('logger', () => { try { const { initLogger } = require('./logger'); initLogger(); } catch {} });
  time('migrateDataDir', () => { try { const { migrateDataDir } = require('./dirs'); migrateDataDir(); } catch {} });
  time('migrateSecrets', migrateSecrets);
  time('migratePluginSecrets', () => {
    try {
      const { migratePluginSecrets } = require('./plugins/registry');
      migratePluginSecrets();
    } catch (e) { console.warn('[init] migratePluginSecrets failed:', (e as Error).message); }
  });
  time('cleanupNotifications', () => { try { const { cleanupNotifications } = require('./notifications'); cleanupNotifications(); } catch {} });
  time('autoDetectAgents', autoDetectAgents);
  time('logToolStatus', () => {
    try { const { logToolStatus } = require('./health'); logToolStatus(); }
    catch (e) { console.warn('[tools] health check failed:', (e as Error).message); }
  });
  time('installForgeSkills', () => {
    try {
      const { installForgeSkills } = require('./workspace/skill-installer');
      installForgeSkills('', '', '', Number(process.env.PORT) || 8403);
      console.log('[init] Forge skills installed/updated');
    } catch {}
  });

  // Sync help docs + CLAUDE.md to data dir on startup
  time('syncHelpDocs', () => {
    try {
      const { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } = require('node:fs');
      const { join: joinPath } = require('node:path');
      const { getConfigDir, getDataDir } = require('./dirs');
      const helpDir = joinPath(getConfigDir(), 'help');
      const sourceDir = joinPath(process.cwd(), 'lib', 'help-docs');
      if (existsSync(sourceDir)) {
        if (!existsSync(helpDir)) mkdirSync(helpDir, { recursive: true });
        for (const f of readdirSync(sourceDir)) {
          if (f.endsWith('.md')) writeFileSync(joinPath(helpDir, f), readFileSync(joinPath(sourceDir, f)));
        }
        const claudeMd = joinPath(helpDir, 'CLAUDE.md');
        if (existsSync(claudeMd)) writeFileSync(joinPath(getDataDir(), 'CLAUDE.md'), readFileSync(claudeMd));
      }
    } catch {}
  });

  // Sync skills registry (async, non-blocking) — on startup + every 30 min
  try {
    const { syncSkills } = require('./skills');
    syncSkills().catch(() => {});
    setInterval(() => { syncSkills().catch(() => {}); }, 60 * 60 * 1000);
  } catch {}

  // One-shot migration: move pre-existing connector rows out of the
  // plugin registry and into <dataDir>/connectors/. Idempotent — safe
  // to call on every boot. Must run BEFORE syncRegistry so the
  // migrated rows are visible to the sync layer (which uses
  // installed_version to decide what to refresh).
  try {
    time('migrateConnectorConfigs', () => {
      const { migrateConnectorConfigs } = require('./connectors/migration');
      migrateConnectorConfigs();
    });
  } catch (err) {
    console.warn('[connectors] migration failed:', err);
  }

  // Sync connectors registry (async, non-blocking). Refresh installed
  // manifests in case the user has older versions on disk. Same cadence
  // as skills.
  try {
    const { syncRegistry } = require('./connectors/sync');
    syncRegistry({ refreshInstalled: true }).catch(() => {});
    setInterval(() => {
      syncRegistry({ refreshInstalled: true }).catch(() => {});
    }, 60 * 60 * 1000);
  } catch {}

  // Usage scanner — defer to next tick so it doesn't block ensureInitialized().
  // On a host with hundreds of project dirs in ~/.claude/projects/, the
  // synchronous readdirSync + statSync loop can take 5-10s; running it on
  // the critical path of the first API request made startup feel hung.
  // setImmediate keeps it in the same process but yields the event loop first.
  setImmediate(() => {
    time('scanUsage (initial)', () => {
      try { const { scanUsage } = require('./usage-scanner'); scanUsage(); } catch {}
    });
  });
  time('require usage-scanner + hourly', () => {
    try {
      const { scanUsage } = require('./usage-scanner');
      setInterval(() => { try { scanUsage(); } catch {} }, 60 * 60 * 1000);
    } catch {}
  });

  // Task runner is safe in every worker (DB-level coordination)
  time('ensureRunnerStarted', ensureRunnerStarted);

  // Session watcher is safe (file-based, idempotent)
  time('startWatcherLoop', startWatcherLoop);

  // Pipeline scheduler — periodic execution + issue scanning for project-bound workflows
  time('pipeline-scheduler', () => {
    try {
      const { startScheduler } = require('./pipeline-scheduler');
      startScheduler();
    } catch {}
  });

  // Jobs scheduler — periodic connector polls that fan out to pipelines / chat
  time('jobs-scheduler', () => {
    try {
      const { startScheduler: startJobsScheduler } = require('./jobs/scheduler');
      startJobsScheduler();
    } catch (e) { console.error('[jobs-scheduler] start failed', e); }
  });

  // If services are managed externally (forge-server), skip
  if (process.env.FORGE_EXTERNAL_SERVICES === '1') {
    // Password display
    const admin = getAdminPassword();
    if (admin) {
      console.log(`[init] Admin password: configured`);
    } else {
      console.log('[init] No admin password set — configure in Settings');
    };
    return;
  }

  // Standalone mode (pnpm dev without forge-server) — start everything here
  const admin2 = getAdminPassword();
  if (admin2) {
    console.log(`[init] Admin password: configured`);
  } else {
    console.log('[init] No admin password set — configure in Settings');
  }

  startTelegramBot(); // registers task event listener only
  startTerminalProcess();
  startTelegramProcess(); // spawns telegram-standalone
  startWorkspaceProcess(); // spawns workspace-standalone
  startBrowserBridgeProcess(); // spawns browser-bridge-standalone
  startChatProcess(); // spawns chat-standalone

  const settings = loadSettings();
  if (settings.tunnelAutoStart) {
    startTunnel().then(result => {
      if (result.url) console.log(`[init] Tunnel started: ${result.url}`);
      else if (result.error) console.log(`[init] Tunnel failed: ${result.error}`);
    });
  }

  console.log('[init] Background services started');
}

/** Restart Telegram bot (e.g. after settings change) */
export function restartTelegramBot() {
  stopTelegramBot();
  startTelegramBot();
  // Kill existing telegram process and restart if configured
  if (telegramChild) {
    try { telegramChild.kill('SIGTERM'); } catch {}
    telegramChild = null;
  }
  startTelegramProcess();
}

let telegramChild: ReturnType<typeof spawn> | null = null;

function startTelegramProcess() {
  if (telegramChild) return;
  const settings = loadSettings();
  if (!settings.telegramBotToken || !settings.telegramChatId) return;

  const script = join(process.cwd(), 'lib', 'telegram-standalone.ts');
  telegramChild = spawn('npx', ['tsx', script], {
    stdio: ['ignore', 'inherit', 'inherit'],
    env: { ...process.env, PORT: String(process.env.PORT || 8403) },
    detached: false,
  });
  telegramChild.on('exit', () => { telegramChild = null; });
  console.log('[telegram] Started standalone (pid:', telegramChild.pid, ')');
}

let terminalChild: ReturnType<typeof spawn> | null = null;

function startTerminalProcess() {
  if (terminalChild) return;

  const termPort = Number(process.env.TERMINAL_PORT) || 8404;

  const net = require('node:net');
  const tester = net.createServer();
  tester.once('error', () => {
    console.log(`[terminal] Port ${termPort} already in use, reusing existing`);
  });
  tester.once('listening', () => {
    tester.close();
    const script = join(process.cwd(), 'lib', 'terminal-standalone.ts');
    terminalChild = spawn('npx', ['tsx', script], {
      stdio: ['ignore', 'inherit', 'inherit'],
      env: { ...Object.fromEntries(Object.entries(process.env).filter(([k]) => k !== 'CLAUDECODE')) } as NodeJS.ProcessEnv,
      detached: false,
    });
    terminalChild.on('exit', () => { terminalChild = null; });
    console.log('[terminal] Started standalone server (pid:', terminalChild.pid, ')');
  });
  tester.listen(termPort);
}

let workspaceChild: ReturnType<typeof spawn> | null = null;

function startWorkspaceProcess() {
  if (workspaceChild) return;

  const wsPort = Number(process.env.WORKSPACE_PORT) || 8405;

  const net = require('node:net');
  const tester = net.createServer();
  tester.once('error', () => {
    // Port in use — kill stale process and retry after 2s
    console.log(`[workspace] Port ${wsPort} in use, killing stale process...`);
    try { require('node:child_process').execSync(`lsof -ti:${wsPort} | xargs kill -9 2>/dev/null`, { timeout: 3000 }); } catch {}
    setTimeout(() => {
      if (!workspaceChild) launchWorkspaceDaemon();
    }, 2000);
  });
  tester.once('listening', () => {
    tester.close();
    launchWorkspaceDaemon();
  });
  tester.listen(wsPort);
}

function launchWorkspaceDaemon() {
  const script = join(process.cwd(), 'lib', 'workspace-standalone.ts');
  workspaceChild = spawn('npx', ['tsx', script], {
    stdio: ['ignore', 'inherit', 'inherit'],
    env: { ...process.env },
    detached: false,
  });
  workspaceChild.on('exit', () => { workspaceChild = null; });
  console.log('[workspace] Started daemon (pid:', workspaceChild.pid, ')');
}

let chatChild: ReturnType<typeof spawn> | null = null;

function startChatProcess() {
  if (chatChild) return;

  const chatPort = Number(process.env.CHAT_PORT) || 8408;

  const net = require('node:net');
  const tester = net.createServer();
  tester.once('error', () => {
    console.log(`[chat] Port ${chatPort} already in use, reusing existing`);
  });
  tester.once('listening', () => {
    tester.close();
    const script = join(process.cwd(), 'lib', 'chat-standalone.ts');
    chatChild = spawn('npx', ['tsx', script], {
      stdio: ['ignore', 'inherit', 'inherit'],
      env: { ...process.env },
      detached: false,
    });
    chatChild.on('exit', () => { chatChild = null; });
    console.log('[chat] Started standalone (pid:', chatChild.pid, ')');
  });
  tester.listen(chatPort);
}

let bridgeChild: ReturnType<typeof spawn> | null = null;

function startBrowserBridgeProcess() {
  if (bridgeChild) return;

  const bridgePort = Number(process.env.BRIDGE_PORT) || 8407;

  const net = require('node:net');
  const tester = net.createServer();
  tester.once('error', () => {
    console.log(`[bridge] Port ${bridgePort} already in use, reusing existing`);
  });
  tester.once('listening', () => {
    tester.close();
    const script = join(process.cwd(), 'lib', 'browser-bridge-standalone.ts');
    bridgeChild = spawn('npx', ['tsx', script], {
      stdio: ['ignore', 'inherit', 'inherit'],
      env: { ...process.env },
      detached: false,
    });
    bridgeChild.on('exit', () => { bridgeChild = null; });
    console.log('[bridge] Started standalone (pid:', bridgeChild.pid, ')');
  });
  tester.listen(bridgePort);
}
