/**
 * Daemon — The firm's building manager.
 *
 * macOS LaunchAgent integration for running Claw Mode as a
 * persistent background service. The firm starts when you boot.
 * The firm restarts if it crashes. The firm never sleeps.
 *
 * Inspired by OpenClaw's daemon module pattern.
 *
 * Commands:
 *   lavern claw daemon install   — Generate plist + load into launchd
 *   lavern claw daemon uninstall — Unload + remove plist
 *   lavern claw daemon status    — Show launchd service status
 *   lavern claw daemon logs      — Tail the log files
 */

import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { execSync, spawn } from 'node:child_process';
import { ensureDir } from '../utils/fs-helpers.js';
import { config } from '../config.js';
import { loadProfile } from './init.js';

// ── Constants ────────────────────────────────────────────────────────────

const LABEL = 'com.lavern.claw';
const PLIST_DIR = path.join(os.homedir(), 'Library', 'LaunchAgents');
const PLIST_PATH = path.join(PLIST_DIR, `${LABEL}.plist`);

function logDir(): string {
  return path.join(config.claw.dir, 'logs');
}

// ── Plist Generation ─────────────────────────────────────────────────────

function generatePlist(): string {
  // Find the node binary
  let nodePath: string;
  try {
    nodePath = execSync('which node', { encoding: 'utf-8' }).trim();
  } catch {
    nodePath = '/usr/local/bin/node';
  }

  // Find tsx for development mode
  let tsxPath: string;
  try {
    tsxPath = execSync('which tsx', { encoding: 'utf-8' }).trim();
  } catch {
    tsxPath = path.join(process.cwd(), 'node_modules', '.bin', 'tsx');
  }

  const workDir = process.cwd();
  const entryPoint = path.join(workDir, 'src', 'index.ts');
  const logs = logDir();

  // Keychain wrapper script path (generated during install)
  const wrapperPath = path.join(logs, 'start-lavern.sh');

  // Use tsx in dev, node+dist in production
  // Wrap with the Keychain loader script for secure API key retrieval
  const useCompiled = fs.existsSync(path.join(workDir, 'dist', 'index.js'));
  const innerArgs = useCompiled
    ? [nodePath, path.join(workDir, 'dist', 'index.js'), '--serve', '--claw']
    : [tsxPath, entryPoint, '--serve', '--claw'];
  const programArgs = ['/bin/bash', wrapperPath, ...innerArgs];

  return `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>${LABEL}</string>

  <key>ProgramArguments</key>
  <array>
${programArgs.map(a => `    <string>${escapeXml(a)}</string>`).join('\n')}
  </array>

  <key>WorkingDirectory</key>
  <string>${escapeXml(workDir)}</string>

  <key>RunAtLoad</key>
  <true/>

  <key>KeepAlive</key>
  <true/>

  <key>StandardOutPath</key>
  <string>${escapeXml(path.join(logs, 'claw.stdout.log'))}</string>

  <key>StandardErrorPath</key>
  <string>${escapeXml(path.join(logs, 'claw.stderr.log'))}</string>

  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key>
    <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    <key>NODE_ENV</key>
    <string>production</string>
    <key>HOME</key>
    <string>${escapeXml(os.homedir())}</string>
  </dict>

  <key>ThrottleInterval</key>
  <integer>10</integer>
</dict>
</plist>`;
}

function escapeXml(s: string): string {
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}

// ── Service Management ───────────────────────────────────────────────────

export interface DaemonStatus {
  installed: boolean;
  running: boolean;
  pid?: number;
  /** Non-zero exit code when the service is loaded but not currently running. */
  lastExitStatus?: number;
  label: string;
  plistPath: string;
  logDir: string;
}

export function installDaemon(): void {
  // Create log directory
  const logs = logDir();
  ensureDir(logs);
  ensureDir(PLIST_DIR);

  // Generate wrapper script — local profiles run on Ollama and don't need
  // an Anthropic key, so we skip the Keychain/.env check entirely. Otherwise
  // the wrapper would exit 1 in a KeepAlive loop and the daemon would silently
  // crash-loop forever.
  const profile = loadProfile(config.claw.dir);
  const processingMode = profile?.processing ?? 'local';
  const wrapperPath = path.join(logs, 'start-lavern.sh');
  const envPath = path.join(process.cwd(), '.env');
  const wrapperContent = processingMode === 'local'
    ? `#!/bin/bash
# Auto-generated by lavern claw daemon install (local-only profile)
# No Anthropic key required — processing runs on Ollama.
exec "$@"
`
    : `#!/bin/bash
# Auto-generated by lavern claw daemon install
# Reads ANTHROPIC_API_KEY from macOS Keychain, falls back to .env file.
export ANTHROPIC_API_KEY="$(security find-generic-password -a lavern -s anthropic-api-key -w 2>/dev/null)"
if [ -z "$ANTHROPIC_API_KEY" ] && [ -f "${envPath}" ]; then
  export $(grep -v '^#' "${envPath}" | grep ANTHROPIC_API_KEY | xargs)
fi
if [ -z "$ANTHROPIC_API_KEY" ]; then
  echo "ERROR: No API key in Keychain or .env. Run: ./scripts/keychain-setup.sh store" >&2
  exit 1
fi
exec "$@"
`;
  fs.writeFileSync(wrapperPath, wrapperContent, { mode: 0o755 });
  console.log(`\nWrapper written to ${wrapperPath} (${processingMode === 'local' ? 'local mode — no API key required' : 'reads ANTHROPIC_API_KEY from Keychain/.env'})`);

  // Generate and write plist
  const plist = generatePlist();
  fs.writeFileSync(PLIST_PATH, plist, 'utf-8');

  console.log(`Plist written to ${PLIST_PATH}`);

  // Load into launchd
  try {
    execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: 'inherit' });
    console.log(`Service loaded: ${LABEL}`);
    console.log(`\nThe firm is now running.`);
    console.log(`  Logs: ${logs}/`);
    const host = config.baseUrl || `http://localhost:${config.port}`;
    console.log(`  Dashboard: ${host}/dashboard/`);
    console.log(`  API: ${host}/api/claw/status`);
    console.log(`\n  Stop: lavern claw daemon uninstall\n`);
  } catch (err) {
    console.error('Failed to load service:', err);
    console.log('\nTry manually: launchctl load -w ' + PLIST_PATH);
  }
}

export function uninstallDaemon(): void {
  if (!fs.existsSync(PLIST_PATH)) {
    console.log(`\nNo service found at ${PLIST_PATH}\n`);
    return;
  }

  try {
    execSync(`launchctl unload -w "${PLIST_PATH}"`, { stdio: 'inherit' });
  } catch {
    // May already be unloaded
  }

  fs.unlinkSync(PLIST_PATH);
  console.log(`\nService unloaded and plist removed.`);
  console.log(`The firm has closed for the day.\n`);
}

export function getDaemonStatus(): DaemonStatus {
  const status: DaemonStatus = {
    installed: fs.existsSync(PLIST_PATH),
    running: false,
    label: LABEL,
    plistPath: PLIST_PATH,
    logDir: logDir(),
  };

  if (status.installed) {
    try {
      const output = execSync(`launchctl list ${LABEL} 2>/dev/null`, { encoding: 'utf-8' });
      // "Present in launchctl" is not the same as "running" — a crash-looping
      // service appears here too. Distinguish: a live PID means actually running;
      // no PID with a non-zero LastExitStatus means loaded-but-failing.
      const pidMatch = output.match(/"PID"\s*=\s*(\d+)/);
      const exitMatch = output.match(/"LastExitStatus"\s*=\s*(-?\d+)/);
      if (pidMatch) {
        status.pid = parseInt(pidMatch[1], 10);
        status.running = true;
      } else if (exitMatch) {
        status.lastExitStatus = parseInt(exitMatch[1], 10);
        status.running = false;
      }
    } catch {
      // Not loaded
    }
  }

  return status;
}

export function printDaemonStatus(): void {
  const status = getDaemonStatus();

  console.log(`\n  Service: ${status.label}`);
  console.log(`  Installed: ${status.installed ? 'yes' : 'no'}`);
  if (status.running) {
    console.log(`  Running: yes (PID ${status.pid})`);
  } else if (status.installed && status.lastExitStatus !== undefined && status.lastExitStatus !== 0) {
    console.log(`  Running: no — loaded but failing (last exit code ${status.lastExitStatus}, see logs)`);
  } else {
    console.log(`  Running: no`);
  }
  console.log(`  Plist: ${status.plistPath}`);
  console.log(`  Logs: ${status.logDir}/\n`);

  if (!status.installed) {
    console.log('  Run `lavern claw daemon install` to start the firm.\n');
  }
}

export function tailLogs(): void {
  const logs = logDir();
  const stdout = path.join(logs, 'claw.stdout.log');
  const stderr = path.join(logs, 'claw.stderr.log');

  if (!fs.existsSync(stdout) && !fs.existsSync(stderr)) {
    console.log(`\nNo log files found at ${logs}/`);
    console.log('Is the daemon running?\n');
    return;
  }

  console.log(`\nTailing logs from ${logs}/\n`);

  try {
    // Use tail -f to follow both log files
    const files = [stdout, stderr].filter(f => fs.existsSync(f));
    const tail = spawn('tail', ['-f', ...files], { stdio: 'inherit' });

    process.on('SIGINT', () => {
      tail.kill('SIGTERM');
      tail.on('exit', () => process.exit(0));
      // Force exit after 2s if tail doesn't terminate
      setTimeout(() => process.exit(0), 2000).unref();
    });
  } catch (err) {
    console.error('Failed to tail logs:', err);
  }
}

// ── CLI Handler ──────────────────────────────────────────────────────────

export async function runDaemon(args: string[]): Promise<void> {
  const subcommand = args[0] ?? 'status';

  if (process.platform !== 'darwin') {
    console.error('\nDaemon mode is currently macOS-only (launchd).');
    console.error('For Linux, use systemd: systemctl --user ...');
    console.error('For manual mode: lavern --serve --claw\n');
    return;
  }

  switch (subcommand) {
    case 'install':
      installDaemon();
      break;
    case 'uninstall':
      uninstallDaemon();
      break;
    case 'status':
      printDaemonStatus();
      break;
    case 'logs':
      tailLogs();
      break;
    default:
      console.log(`\nUnknown daemon command: ${subcommand}`);
      console.log('Usage: lavern claw daemon [install|uninstall|status|logs]\n');
  }
}
