import { spawnSync } from 'node:child_process';
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { homedir } from 'node:os';
import type { Command } from 'commander';
import { createConfigService } from '../config/config-service.js';
import { DEFAULT_MOSAIC_HOME } from '../constants.js';

// ── Hooks management helpers ──────────────────────────────────────────────────

const DISABLED_PREFIX = '_disabled_';

/** Resolve the ~/.claude directory (allow override via CLAUDE_HOME env var). */
function getClaudeHome(): string {
  return process.env['CLAUDE_HOME'] ?? join(homedir(), '.claude');
}

interface HookEntry {
  type?: string;
  command?: string;
  args?: unknown[];
  [key: string]: unknown;
}

interface HookTrigger {
  matcher?: string;
  hooks?: HookEntry[];
}

interface HooksConfig {
  name?: string;
  hooks?: Record<string, HookTrigger[]>;
  [key: string]: unknown;
}

function readInstalledHooksConfig(claudeHome: string): HooksConfig | null {
  const p = join(claudeHome, 'hooks-config.json');
  if (!existsSync(p)) return null;
  try {
    return JSON.parse(readFileSync(p, 'utf-8')) as HooksConfig;
  } catch {
    return null;
  }
}

function writeInstalledHooksConfig(claudeHome: string, config: HooksConfig): void {
  const p = join(claudeHome, 'hooks-config.json');
  writeFileSync(p, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
}

/**
 * Collect a flat list of hook "names" for display purposes.
 * A hook name is `<EventName>/<matcher>` (e.g. `PostToolUse/Write|Edit`).
 */
function listHookNames(config: HooksConfig): Array<{ name: string; enabled: boolean }> {
  const results: Array<{ name: string; enabled: boolean }> = [];
  const events = config.hooks ?? {};

  for (const [rawEvent, triggers] of Object.entries(events)) {
    const enabled = !rawEvent.startsWith(DISABLED_PREFIX);
    const event = enabled ? rawEvent : rawEvent.slice(DISABLED_PREFIX.length);

    for (const trigger of triggers) {
      const matcher = trigger.matcher ?? '(any)';
      results.push({ name: `${event}/${matcher}`, enabled });
    }
  }

  return results;
}

/**
 * Resolve mosaicHome from the MOSAIC_HOME env var or the default constant.
 */
function getMosaicHome(): string {
  return process.env['MOSAIC_HOME'] ?? DEFAULT_MOSAIC_HOME;
}

/**
 * Guard: print an error and exit(1) if config has not been initialised.
 */
function assertInitialized(svc: ReturnType<typeof createConfigService>): void {
  if (!svc.isInitialized()) {
    console.error('No config found — run `mosaic wizard` first.');
    process.exit(1);
  }
}

/**
 * Flatten a nested object into dotted-key rows for table display.
 */
function flattenConfig(obj: Record<string, unknown>, prefix = ''): Array<[string, string]> {
  const rows: Array<[string, string]> = [];
  for (const [k, v] of Object.entries(obj)) {
    const key = prefix ? `${prefix}.${k}` : k;
    if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
      rows.push(...flattenConfig(v as Record<string, unknown>, key));
    } else {
      rows.push([key, v === undefined || v === null ? '' : String(v)]);
    }
  }
  return rows;
}

/**
 * Print rows as a padded ASCII table.
 */
function printTable(rows: Array<[string, string]>): void {
  if (rows.length === 0) {
    console.log('(no config values)');
    return;
  }
  const maxKey = Math.max(...rows.map(([k]) => k.length));
  const header = `${'Key'.padEnd(maxKey)}  Value`;
  const divider = '-'.repeat(header.length);
  console.log(header);
  console.log(divider);
  for (const [k, v] of rows) {
    console.log(`${k.padEnd(maxKey)}  ${v}`);
  }
}

export function registerConfigCommand(program: Command): void {
  const cmd = program
    .command('config')
    .description('Manage Mosaic framework configuration')
    .configureHelp({ sortSubcommands: true });

  // ── config show ─────────────────────────────────────────────────────────

  cmd
    .command('show')
    .description('Print the current resolved config')
    .option('-f, --format <format>', 'Output format: table or json', 'table')
    .action(async (opts: { format: string }) => {
      const mosaicHome = getMosaicHome();
      const svc = createConfigService(mosaicHome, mosaicHome);
      assertInitialized(svc);

      const config = await svc.readAll();

      if (opts.format === 'json') {
        console.log(JSON.stringify(config, null, 2));
        return;
      }

      // Default: table
      const rows = flattenConfig(config as unknown as Record<string, unknown>);
      printTable(rows);
    });

  // ── config get <key> ────────────────────────────────────────────────────

  cmd
    .command('get <key>')
    .description('Print a single config value (supports dotted keys, e.g. soul.agentName)')
    .action(async (key: string) => {
      const mosaicHome = getMosaicHome();
      const svc = createConfigService(mosaicHome, mosaicHome);
      assertInitialized(svc);

      const value = await svc.getValue(key);
      if (value === undefined) {
        console.error(`Key "${key}" not found.`);
        process.exit(1);
      }
      if (typeof value === 'object') {
        console.log(JSON.stringify(value, null, 2));
      } else {
        console.log(String(value));
      }
    });

  // ── config set <key> <value> ────────────────────────────────────────────

  cmd
    .command('set <key> <value>')
    .description(
      'Set a config value and persist (supports dotted keys, e.g. soul.agentName "Jarvis")',
    )
    .action(async (key: string, value: string) => {
      const mosaicHome = getMosaicHome();
      const svc = createConfigService(mosaicHome, mosaicHome);
      assertInitialized(svc);

      let previous: unknown;
      try {
        previous = await svc.setValue(key, value);
      } catch (err) {
        console.error(err instanceof Error ? err.message : String(err));
        process.exit(1);
      }

      const prevStr = previous === undefined ? '(unset)' : String(previous);
      console.log(`${key}`);
      console.log(`  old: ${prevStr}`);
      console.log(`  new: ${value}`);
    });

  // ── config edit ─────────────────────────────────────────────────────────

  cmd
    .command('edit')
    .description('Open the config directory in $EDITOR (or vi)')
    .option('-s, --section <section>', 'Open a specific section file: soul | user | tools')
    .action(async (opts: { section?: string }) => {
      const mosaicHome = getMosaicHome();
      const svc = createConfigService(mosaicHome, mosaicHome);
      assertInitialized(svc);

      const editor = process.env['EDITOR'] ?? 'vi';

      let targetPath: string;
      if (opts.section) {
        const validSections = ['soul', 'user', 'tools'] as const;
        if (!validSections.includes(opts.section as (typeof validSections)[number])) {
          console.error(`Invalid section "${opts.section}". Choose: soul, user, tools`);
          process.exit(1);
        }
        targetPath = svc.getConfigPath(opts.section as 'soul' | 'user' | 'tools');
      } else {
        targetPath = svc.getConfigPath();
      }

      const result = spawnSync(editor, [targetPath], { stdio: 'inherit' });

      if (result.error) {
        console.error(`Failed to open editor: ${result.error.message}`);
        process.exit(1);
      }

      if (result.status !== 0) {
        console.error(`Editor exited with code ${String(result.status ?? 1)}`);
        process.exit(result.status ?? 1);
      }

      // Re-read after edit and report any issues
      try {
        await svc.readAll();
        console.log('Config looks valid.');
      } catch (err) {
        console.error('Warning: config may have validation issues:');
        console.error(err instanceof Error ? err.message : String(err));
        process.exit(1);
      }
    });

  // ── config hooks ────────────────────────────────────────────────────────

  const hookCmd = cmd.command('hooks').description('Manage Mosaic hooks installed in ~/.claude/');

  hookCmd
    .command('list')
    .description('List installed hooks and their enabled/disabled status')
    .action(() => {
      const claudeHome = getClaudeHome();
      const config = readInstalledHooksConfig(claudeHome);

      if (!config) {
        console.log(
          `No hooks-config.json found at ${claudeHome}.\n` +
            'Run `mosaic wizard` to install hooks, or copy hooks-config.json manually.',
        );
        return;
      }

      const entries = listHookNames(config);

      if (entries.length === 0) {
        console.log('No hooks defined in hooks-config.json.');
        return;
      }

      const maxName = Math.max(...entries.map((e) => e.name.length));
      const header = `${'Hook'.padEnd(maxName)}  Status`;
      console.log(header);
      console.log('-'.repeat(header.length));

      for (const { name, enabled } of entries) {
        console.log(`${name.padEnd(maxName)}  ${enabled ? 'enabled' : 'disabled'}`);
      }
    });

  hookCmd
    .command('disable <name>')
    .description('Disable a hook by name (prefix with _disabled_). Use "list" to see hook names.')
    .action((name: string) => {
      const claudeHome = getClaudeHome();
      const config = readInstalledHooksConfig(claudeHome);

      if (!config) {
        console.error(
          `No hooks-config.json found at ${claudeHome}.\n` +
            'Nothing to disable. Run `mosaic wizard` to install hooks first.',
        );
        process.exit(1);
      }

      const events = config.hooks ?? {};
      // Support matching by event key or by event/matcher composite
      const [targetEvent, targetMatcher] = name.split('/');

      // Find the event key (may already have DISABLED_PREFIX)
      const existingKey = Object.keys(events).find(
        (k) =>
          k === targetEvent ||
          k === `${DISABLED_PREFIX}${targetEvent}` ||
          k.replace(DISABLED_PREFIX, '') === targetEvent,
      );

      if (!existingKey) {
        console.error(`Hook event "${targetEvent}" not found.`);
        console.error('Run `mosaic config hooks list` to see available hooks.');
        process.exit(1);
      }

      if (existingKey.startsWith(DISABLED_PREFIX)) {
        console.log(`Hook "${name}" is already disabled.`);
        return;
      }

      const disabledKey = `${DISABLED_PREFIX}${existingKey}`;
      const triggers = events[existingKey];
      delete events[existingKey];

      // If a matcher was specified, only disable that trigger
      if (targetMatcher && triggers) {
        events[disabledKey] = triggers.filter((t) => t.matcher === targetMatcher);
        events[existingKey] = triggers.filter((t) => t.matcher !== targetMatcher);
        if ((events[existingKey] ?? []).length === 0) delete events[existingKey];
      } else {
        events[disabledKey] = triggers ?? [];
      }

      config.hooks = events;
      writeInstalledHooksConfig(claudeHome, config);
      console.log(`Hook "${name}" disabled.`);
    });

  hookCmd
    .command('enable <name>')
    .description('Re-enable a previously disabled hook.')
    .action((name: string) => {
      const claudeHome = getClaudeHome();
      const config = readInstalledHooksConfig(claudeHome);

      if (!config) {
        console.error(
          `No hooks-config.json found at ${claudeHome}.\n` +
            'Nothing to enable. Run `mosaic wizard` to install hooks first.',
        );
        process.exit(1);
      }

      const events = config.hooks ?? {};
      const targetEvent = name.split('/')[0] ?? name;
      const disabledKey = `${DISABLED_PREFIX}${targetEvent}`;

      if (!events[disabledKey]) {
        // Check if it's already enabled
        if (events[targetEvent]) {
          console.log(`Hook "${name}" is already enabled.`);
        } else {
          console.error(`Disabled hook "${name}" not found.`);
          console.error('Run `mosaic config hooks list` to see available hooks.');
          process.exit(1);
        }
        return;
      }

      const triggers = events[disabledKey];
      delete events[disabledKey];
      events[targetEvent] = triggers ?? [];

      config.hooks = events;
      writeInstalledHooksConfig(claudeHome, config);
      console.log(`Hook "${name}" enabled.`);
    });

  // ── config path ─────────────────────────────────────────────────────────

  cmd
    .command('path')
    .description('Print the active config directory path (for scripting)')
    .option(
      '-s, --section <section>',
      'Print path for a specific section file: soul | user | tools',
    )
    .action(async (opts: { section?: string }) => {
      const mosaicHome = getMosaicHome();
      const svc = createConfigService(mosaicHome, mosaicHome);

      if (opts.section) {
        const validSections = ['soul', 'user', 'tools'] as const;
        if (!validSections.includes(opts.section as (typeof validSections)[number])) {
          console.error(`Invalid section "${opts.section}". Choose: soul, user, tools`);
          process.exit(1);
        }
        console.log(svc.getConfigPath(opts.section as 'soul' | 'user' | 'tools'));
      } else {
        console.log(svc.getConfigPath());
      }
    });
}
