/**
 * CLI Router for Onebox
 */

import { logger } from './logging.js';
import { projectInfo } from './info.js';
import { getErrorMessage } from './utils/error.js';
import { OneboxDatabase } from './database/index.js';
import { normalizePasskeyHostname } from './utils/domain.js';
import { Onebox } from './classes/onebox.js';
import { OneboxDaemon } from './classes/daemon.js';
import { OneboxSystemd } from './classes/systemd.js';
import { OneboxUpdateManager } from './classes/update-manager.js';
import type * as servezoneInterfaces from '@serve.zone/interfaces';

type IAppStoreVersionConfig = servezoneInterfaces.appstore.IAppStoreVersionConfig;

export async function runCli(): Promise<void> {
  const args = process.argv.slice(2);

  if (args.length === 0 || (args.length === 1 && (args[0] === '--help' || args[0] === '-h'))) {
    printHelp();
    return;
  }

  if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
    console.log(`${projectInfo.name} v${projectInfo.version}`);
    return;
  }

  const command = args[0];
  const subcommand = args[1];

  try {
    // === LIGHTWEIGHT COMMANDS (no init()) ===
    if (command === 'systemd') {
      await handleSystemdCommand(subcommand, args.slice(2));
      return;
    }

    if (command === 'upgrade') {
      await handleUpgradeCommand();
      return;
    }

    // === HEAVY COMMANDS (require full init()) ===
    if (command === 'auth') {
      await handleAuthCommand(subcommand, args.slice(2));
      return;
    }


    // Server command has special handling (doesn't shut down)
    if (command === 'server') {
      const onebox = new Onebox();
      await onebox.init();
      await handleServerCommand(onebox, args.slice(1));
      // Server command runs forever (or until Ctrl+C), so this never returns
      return;
    }

    // Initialize Onebox
    const onebox = new Onebox();
    await onebox.init();

    // Route commands
    switch (command) {
      case 'service':
        await handleServiceCommand(onebox, subcommand, args.slice(2));
        break;

      case 'registry':
        await handleRegistryCommand(onebox, subcommand, args.slice(2));
        break;

      case 'dns':
        await handleDnsCommand(onebox, subcommand, args.slice(2));
        break;

      case 'ssl':
        await handleSslCommand(onebox, subcommand, args.slice(2));
        break;

      case 'appstore':
        await handleAppStoreCommand(onebox, subcommand, args.slice(2));
        break;

      case 'proxy':
      case 'nginx':
        await handleNginxCommand(onebox, subcommand, args.slice(2));
        break;

      case 'config':
        await handleConfigCommand(onebox, subcommand, args.slice(2));
        break;

      case 'status':
        await handleStatusCommand(onebox);
        break;

      default:
        logger.error(`Unknown command: ${command}`);
        printHelp();
        process.exit(1);
    }

    // Cleanup
    await onebox.shutdown();
  } catch (error) {
    logger.error(getErrorMessage(error));
    process.exit(1);
  }
}

// Service commands
async function handleServiceCommand(onebox: Onebox, subcommand: string, args: string[]) {
  switch (subcommand) {
    case 'add': {
      const name = args[0];
      const image = getArg(args, '--image');
      const domain = getArg(args, '--domain');
      const port = parseInt(getArg(args, '--port') || '80', 10);
      const envVars = parseEnvArgs(args);

      requireValue(name, 'service name');
      requireValue(image, '--image');
      assertValidPort(port, '--port');

      await onebox.services.deployService({ name, image, port, domain, envVars });
      break;
    }

    case 'remove':
      await onebox.services.removeService(args[0]);
      break;

    case 'start':
      await onebox.services.startService(args[0]);
      break;

    case 'stop':
      await onebox.services.stopService(args[0]);
      break;

    case 'restart':
      await onebox.services.restartService(args[0]);
      break;

    case 'list': {
      const services = await onebox.services.listServices();
      logger.table(
        ['Name', 'Image', 'Status', 'Domain', 'Port'],
        services.map((s) => [s.name, s.image, s.status, s.domain || '-', s.port.toString()])
      );
      break;
    }

    case 'logs': {
      const logs = await onebox.services.getServiceLogs(args[0]);
      console.log(logs);
      break;
    }

    default:
      logger.error(`Unknown service subcommand: ${subcommand}`);
  }
}

// Registry commands
async function handleRegistryCommand(onebox: Onebox, subcommand: string, args: string[]) {
  switch (subcommand) {
    case 'add': {
      const url = getArg(args, '--url');
      const username = getArg(args, '--username');
      const password = getArg(args, '--password');
      requireValue(url, '--url');
      await onebox.registries.addRegistry(url, username, password);
      break;
    }

    case 'remove':
      await onebox.registries.removeRegistry(getArg(args, '--url'));
      break;

    case 'list': {
      const registries = await onebox.registries.listRegistries();
      logger.table(
        ['URL', 'Username'],
        registries.map((r) => [r.url, r.username])
      );
      break;
    }

    default:
      logger.error(`Unknown registry subcommand: ${subcommand}`);
  }
}

// App Store commands
async function handleAppStoreCommand(onebox: Onebox, subcommand: string, args: string[]) {
  switch (subcommand) {
    case 'list': {
      const apps = await onebox.appStore.getApps();
      logger.table(
        ['ID', 'Name', 'Category', 'Latest'],
        apps.map((app) => [app.id, app.name, app.category, app.latestVersion])
      );
      break;
    }

    case 'config': {
      const appId = args[0];
      requireValue(appId, 'app id');
      const appMeta = await onebox.appStore.getAppMeta(appId);
      const version = getArg(args, '--version') || appMeta.latestVersion;
      const config = await onebox.appStore.getAppVersionConfig(appId, version);
      console.log(JSON.stringify({ appMeta, version, config }, null, 2));
      break;
    }

    case 'install': {
      const appId = args[0];
      requireValue(appId, 'app id');

      const appMeta = await onebox.appStore.getAppMeta(appId);
      const version = getArg(args, '--version') || appMeta.latestVersion;
      const serviceName = getArg(args, '--name') || appId;
      const domain = getArg(args, '--domain');
      const portArg = getArg(args, '--port');
      const port = portArg ? parseInt(portArg, 10) : undefined;
      const autoDNS = getBooleanArg(args, '--auto-dns', true);

      requireValue(serviceName, '--name');
      if (port !== undefined) {
        assertValidPort(port, '--port');
      }

      const service = await onebox.appStore.installApp({
        appId,
        version,
        serviceName,
        domain,
        port,
        autoDNS,
        envVars: parseEnvArgs(args),
      });

      logger.success(`Installed ${appMeta.name} ${version} as ${service.name}`);
      if (service.domain) {
        logger.info(`Route: https://${service.domain}`);
      }
      break;
    }

    default:
      logger.error(`Unknown appstore subcommand: ${subcommand}`);
      logger.info('Available: list, config, install');
  }
}

// DNS commands
async function handleDnsCommand(onebox: Onebox, subcommand: string, args: string[]) {
  switch (subcommand) {
    case 'add':
      await onebox.dns.addDNSRecord(args[0]);
      break;

    case 'remove':
      await onebox.dns.removeDNSRecord(args[0]);
      break;

    case 'list': {
      const records = await onebox.dns.listDNSRecords();
      logger.table(
        ['Domain', 'Type', 'Value'],
        records.map((r) => [r.domain, r.type, r.value])
      );
      break;
    }

    case 'sync':
      await onebox.dns.syncFromCloudflare();
      break;

    default:
      logger.error(`Unknown dns subcommand: ${subcommand}`);
  }
}

// SSL commands
async function handleSslCommand(onebox: Onebox, subcommand: string, args: string[]) {
  switch (subcommand) {
    case 'renew':
      if (args[0]) {
        await onebox.ssl.renewCertificate(args[0]);
      } else {
        await onebox.ssl.renewExpiring();
      }
      break;

    case 'list': {
      const certs = await onebox.ssl.listCertificates();
      logger.table(
        ['Domain', 'Expiry', 'Issuer'],
        certs.map((c) => [c.domain, new Date(c.expiryDate).toISOString(), c.issuer])
      );
      break;
    }

    case 'force-renew':
      await onebox.ssl.renewCertificate(args[0]);
      break;

    default:
      logger.error(`Unknown ssl subcommand: ${subcommand}`);
  }
}

// Reverse proxy commands (formerly nginx commands)
async function handleNginxCommand(onebox: Onebox, subcommand: string, _args: string[]) {
  switch (subcommand) {
    case 'reload':
      // Reload routes and certificates
      await onebox.reverseProxy.reloadRoutes();
      await onebox.reverseProxy.reloadCertificates();
      logger.success('Reverse proxy configuration reloaded');
      break;

    case 'test':
      // Verify reverse proxy is running
      const proxyStatus = onebox.reverseProxy.getStatus();
      if (proxyStatus.http.running || proxyStatus.https.running) {
        logger.success('Reverse proxy is running');
        logger.info(`HTTP: ${proxyStatus.http.running ? 'active' : 'inactive'} (port ${proxyStatus.http.port})`);
        logger.info(`HTTPS: ${proxyStatus.https.running ? 'active' : 'inactive'} (port ${proxyStatus.https.port})`);
        logger.info(`Routes: ${proxyStatus.routes}, Certificates: ${proxyStatus.https.certificates}`);
      } else {
        logger.error('Reverse proxy is not running');
      }
      break;

    case 'status': {
      const status = onebox.reverseProxy.getStatus();
      logger.info(`Reverse proxy status:`);
      logger.info(`  HTTP: ${status.http.running ? 'running' : 'stopped'} (port ${status.http.port})`);
      logger.info(`  HTTPS: ${status.https.running ? 'running' : 'stopped'} (port ${status.https.port})`);
      logger.info(`  Routes: ${status.routes}`);
      logger.info(`  Certificates: ${status.https.certificates}`);
      break;
    }

    default:
      logger.error(`Unknown nginx subcommand: ${subcommand}`);
  }
}

// Server command
async function handleServerCommand(onebox: Onebox, args: string[]) {
  const ephemeral = args.includes('--ephemeral');
  const port = parseInt(getArg(args, '--port') || '3000', 10);
  const monitor = args.includes('--monitor') || ephemeral; // ephemeral includes monitoring

  if (ephemeral) {
    // Ensure no daemon is running
    try {
      await OneboxDaemon.ensureNoDaemon();
    } catch (error) {
      logger.error('Cannot start in ephemeral mode: Daemon is already running');
      logger.info('Stop the daemon first: onebox systemd stop');
      logger.info('Or run without --ephemeral to use the existing daemon');
      process.exit(1);
    }
  }

  logger.info('Starting Onebox server...');

  // Start OpsServer (serves new UI + TypedRequest API)
  await onebox.opsServer.start(port);

  // Start monitoring if requested
  if (monitor) {
    logger.info('Starting monitoring loop...');
    onebox.daemon.startMonitoring();
  }

  logger.success(`Onebox server running on http://localhost:${port}`);
  if (ephemeral) {
    logger.info('Running in ephemeral mode - Press Ctrl+C to stop');
  } else {
    logger.info('Press Ctrl+C to stop');
  }

  // Setup signal handlers
  const shutdown = async () => {
    logger.info('Shutting down...');
    if (monitor) {
      onebox.daemon.stopMonitoring();
    }
    await onebox.opsServer.stop();
    await onebox.shutdown();
    process.exit(0);
  };

  process.on('SIGINT', shutdown);
  process.on('SIGTERM', shutdown);

  // Keep alive
  while (true) {
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}

// Systemd service commands (lightweight — no Onebox init)
async function handleSystemdCommand(subcommand: string, _args: string[]) {
  const systemd = new OneboxSystemd();

  switch (subcommand) {
    case 'enable':
      await systemd.enable();
      break;

    case 'disable':
      await systemd.disable();
      break;

    case 'start':
      await systemd.start();
      break;

    case 'stop':
      await systemd.stop();
      break;

    case 'status': {
      const status = await systemd.getStatus();
      logger.info(`Service status: ${status}`);
      break;
    }

    case 'logs':
      await systemd.showLogs();
      break;

    case 'start-daemon': {
      // This is what systemd's ExecStart calls — full init + daemon loop
      const onebox = new Onebox();
      await onebox.init();
      await onebox.daemon.start();
      // start() blocks (keepAlive loop) until SIGTERM/SIGINT
      break;
    }

    default:
      logger.error(`Unknown systemd subcommand: ${subcommand}`);
      logger.info('Available: enable, disable, start, stop, status, logs');
  }
}

// Authentication recovery commands
async function handleAuthCommand(subcommand: string, args: string[]): Promise<void> {
  if (subcommand !== 'reset-passkeys') {
    throw new Error('Unknown auth command. Available: reset-passkeys');
  }
  if (!args.includes('--confirm')) {
    throw new Error(
      'This removes every passkey for the configured administrator. Re-run with --confirm after stopping Onebox.',
    );
  }

  const databasePath = getArg(args, '--database-path')
    || (process.env.ONEBOX_DEV === 'true'
      ? './.nogit/smartdb'
      : '/var/lib/onebox/.nogit/smartdb');
  const database = new OneboxDatabase(databasePath);
  try {
    await database.init();
    const userIdBytes = new Uint8Array(32);
    crypto.getRandomValues(userIdBytes);
    const reset = await database.resetAdminPasskeys(
      process.env.ONEBOX_ADMIN_USERNAME || 'admin',
      Buffer.from(userIdBytes).toString('base64url'),
    );
    if (!reset) {
      throw new Error('The configured Onebox administrator was not found');
    }
  } finally {
    await database.close().catch(() => {});
  }

  logger.success('Configured administrator passkeys were reset.');
  logger.warn('All sessions and passkeys for the configured administrator are now invalid.');
  logger.info(
    'Restart Onebox, then use the new bootstrap code from its local service logs.',
  );
}

// Config commands

async function handleConfigCommand(onebox: Onebox, subcommand: string, args: string[]) {
  switch (subcommand) {
    case 'show': {
      for (const secretKey of onebox.database.getCanonicalSecretSettingKeys()) {
        await onebox.database.getSecretSetting(secretKey);
      }

      const settings = await onebox.database.getAllSettings();
      for (const secretKey of onebox.database.getCanonicalSecretSettingKeys()) {
        if (await onebox.database.hasSecretSetting(secretKey)) {
          settings[secretKey] = '********';
        }
      }

      logger.table(
        ['Key', 'Value'],
        Object.entries(settings).map(([k, v]) => [k, String(v)])
      );
      break;
    }

    case 'set':
      requireValue(args[0], 'setting key');
      requireValue(args[1], 'setting value');
      if (args[0] === 'adminUiDomain') {
        const normalizedDomain = normalizePasskeyHostname(args[1]);
        if (!normalizedDomain) {
          throw new Error(
            'adminUiDomain must be a hostname served over HTTPS on port 443',
          );
        }
        await onebox.assertAdminUiDomainCompatible(normalizedDomain);
        args[1] = normalizedDomain;
      }
      if (onebox.database.isSecretSettingKey(args[0])) {
        await onebox.database.setSecretSetting(args[0], args[1]);
      } else {
        await onebox.database.setSetting(args[0], args[1]);
      }
      logger.success(`Setting ${args[0]} updated`);
      break;

    default:
      logger.error(`Unknown config subcommand: ${subcommand}`);
  }
}

// Status command
async function handleStatusCommand(onebox: Onebox) {
  const status = await onebox.getSystemStatus();
  console.log(JSON.stringify(status, null, 2));
}

// Upgrade command - self-update onebox to latest version
async function handleUpgradeCommand(): Promise<void> {
  // Check if running as root
  if (process.getuid?.() !== 0) {
    logger.error('This command must be run as root to upgrade Onebox.');
    logger.info('Try: sudo onebox upgrade');
    process.exit(1);
  }

  logger.info('Checking for updates...');

  try {
    const updateManager = new OneboxUpdateManager();
    const status = await updateManager.getUpdateStatus({ force: true });
    if (status.error) {
      throw new Error(status.error);
    }

    console.log(`  Current version: ${status.currentVersion}`);
    console.log(`  Latest version:  ${status.latestVersion}`);
    console.log('');

    if (!status.updateAvailable) {
      logger.success('Already up to date!');
      return;
    }

    logger.info(`New version available: ${status.latestVersion}`);
    logger.info('Downloading and installing...');
    console.log('');

    const upgrade = await updateManager.runUpgradeForeground(status);

    console.log('');
    logger.success(upgrade.message);
  } catch (error) {
    logger.error(`Upgrade failed: ${getErrorMessage(error)}`);
    process.exit(1);
  }
}

// Helpers
function getArg(args: string[], flag: string): string {
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith(`${flag}=`)) {
      return arg.slice(flag.length + 1);
    }
    if (arg === flag) {
      const value = args[i + 1];
      return value && !value.startsWith('--') ? value : '';
    }
  }
  return '';
}

function getRepeatedArgs(args: string[], flag: string): string[] {
  const values: string[] = [];
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith(`${flag}=`)) {
      values.push(arg.slice(flag.length + 1));
      continue;
    }
    if (arg === flag) {
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        values.push(value);
        i++;
      }
    }
  }
  return values;
}

function getBooleanArg(args: string[], flag: string, defaultValue: boolean): boolean {
  if (args.includes(`--no-${flag.slice(2)}`)) {
    return false;
  }
  const value = getArg(args, flag);
  if (!value) {
    return args.includes(flag) ? true : defaultValue;
  }
  return !['0', 'false', 'no', 'off'].includes(value.toLowerCase());
}

function parseEnvArgs(args: string[]): Record<string, string> {
  const envVars: Record<string, string> = {};
  for (const envArg of getRepeatedArgs(args, '--env')) {
    const separatorIndex = envArg.indexOf('=');
    if (separatorIndex === -1) {
      throw new Error(`Invalid --env value '${envArg}'. Expected KEY=VALUE.`);
    }
    const key = envArg.slice(0, separatorIndex);
    const value = envArg.slice(separatorIndex + 1);
    requireValue(key, '--env key');
    envVars[key] = value;
  }
  return envVars;
}

function getAppStoreEnvVars(
  configArg: IAppStoreVersionConfig,
  overridesArg: Record<string, string>,
): Record<string, string> {
  const envVars: Record<string, string> = {};
  const missingRequiredEnvVars: string[] = [];

  for (const envVar of configArg.envVars || []) {
    const value = overridesArg[envVar.key] ?? envVar.value ?? '';
    if (envVar.required && !value) {
      missingRequiredEnvVars.push(envVar.key);
    }
    envVars[envVar.key] = value;
  }

  for (const [key, value] of Object.entries(overridesArg)) {
    envVars[key] = value;
  }

  if (missingRequiredEnvVars.length > 0) {
    throw new Error(
      `Missing required app env var(s): ${missingRequiredEnvVars.join(', ')}. Use --env KEY=VALUE.`
    );
  }

  return envVars;
}

function requiresTemplateValue(envVarsArg: Record<string, string>, templateNameArg: string): boolean {
  return Object.values(envVarsArg).some((value) => value.includes(`\${${templateNameArg}}`));
}

function requireValue(valueArg: string | undefined, labelArg: string): asserts valueArg is string {
  if (!valueArg) {
    throw new Error(`Missing required ${labelArg}`);
  }
}

function assertValidPort(portArg: number, labelArg: string): void {
  if (!Number.isInteger(portArg) || portArg <= 0 || portArg > 65535) {
    throw new Error(`Invalid ${labelArg}: ${portArg}`);
  }
}

function printHelp(): void {
  console.log(`
Onebox v${projectInfo.version} - Self-hosted container platform

Usage: onebox <command> [options]

Commands:
  server [--ephemeral] [--port <port>] [--monitor]
      Start HTTP server
      --ephemeral: Run in foreground (development mode, checks no daemon running)
      --port: HTTP server port (default: 3000)
      --monitor: Enable monitoring loop (included with --ephemeral)

  service add <name> --image <image> [--domain <domain>] [--port <port>] [--env KEY=VALUE]
  service remove <name>
  service start <name>
  service stop <name>
  service restart <name>
  service list
  service logs <name>

  registry add --url <url> --username <user> --password <pass>
  registry remove --url <url>
  registry list

  dns add <domain>
  dns remove <domain>
  dns list
  dns sync

  ssl renew [domain]
  ssl list
  ssl force-renew <domain>

  appstore list
  appstore config <app-id> [--version <version>]
  appstore install <app-id> --name <name> [--domain <domain>] [--version <version>] [--env KEY=VALUE]

  proxy reload                  # nginx alias is still supported
  proxy test
  proxy status

  systemd enable             Install and enable systemd service
  systemd disable            Stop, disable, and remove systemd service
  systemd start              Start onebox via systemctl
  systemd stop               Stop onebox via systemctl
  systemd status             Show systemd service status
  systemd logs               Follow service logs (journalctl)

  config show
  config set <key> <value>

  auth reset-passkeys --confirm [--database-path <path>]
      Remove every passkey for the configured administrator using local database access.
      Stop Onebox first; restart it afterward to receive a new bootstrap code.

  status

  upgrade
      Upgrade Onebox to the latest version (requires root)

Options:
  --help, -h     Show this help message
  --version, -v  Show version
  --debug        Enable debug logging

Development Workflow:
  node ./cli.ts.js server --ephemeral --monitor  # Start ephemeral server with monitoring
  onebox service add ...               # In another terminal

Production Workflow:
  onebox systemd enable                # Install and enable systemd service
  onebox systemd start                 # Start via systemctl
  onebox service add ...               # CLI manages services

Examples:
  onebox server --ephemeral            # Start dev server
  onebox service add myapp --image nginx:latest --domain app.example.com --port 80
  onebox appstore install cloudly --name cloudly --domain cloudly.example.com --env SERVEZONE_ADMINACCOUNT=admin:password
  onebox registry add --url registry.example.com --username user --password pass
  onebox systemd enable
  onebox systemd start
`);
}
