import fs from 'node:fs';
import path from 'node:path';

import type { Command } from 'commander';

import { classifyBrief } from './brief-classifier.js';
import { STAGE_LABELS, STAGE_SEQUENCE } from './constants.js';
import { getEffectivePersonas, loadBoardPersonas } from './persona-loader.js';
import { generateRunId, getPipelineStatus, loadManifest, runPipeline } from './pipeline-runner.js';
import type { PipelineOptions, RunManifest, TaskExecutor } from './types.js';

// ---------------------------------------------------------------------------
// Stub executor — used when no real executor is wired at CLI invocation time.
// ---------------------------------------------------------------------------

const stubExecutor: TaskExecutor = {
  async submitTask(task) {
    console.log(`  [forge] stage submitted: ${task.id} (${task.title})`);
  },
  async waitForCompletion(taskId, _timeoutMs) {
    console.log(`  [forge] stage complete:  ${taskId}`);
    return {
      task_id: taskId,
      status: 'completed' as const,
      completed_at: new Date().toISOString(),
      exit_code: 0,
      gate_results: [],
    };
  },
  async getTaskStatus(_taskId) {
    return 'completed' as const;
  },
};

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function formatDuration(startedAt?: string, completedAt?: string): string {
  if (!startedAt || !completedAt) return '-';
  const ms = new Date(completedAt).getTime() - new Date(startedAt).getTime();
  const secs = Math.round(ms / 1000);
  return secs < 60 ? `${secs}s` : `${Math.floor(secs / 60)}m${secs % 60}s`;
}

function printManifestTable(manifest: RunManifest): void {
  console.log(`\nRun ID : ${manifest.runId}`);
  console.log(`Status : ${manifest.status}`);
  console.log(`Brief  : ${manifest.brief}`);
  console.log(`Class  : ${manifest.briefClass} (${manifest.classSource})`);
  console.log(`Updated: ${manifest.updatedAt}`);
  console.log('');
  console.log('Stage'.padEnd(22) + 'Status'.padEnd(14) + 'Duration');
  console.log('-'.repeat(50));
  for (const stage of STAGE_SEQUENCE) {
    const s = manifest.stages[stage];
    if (!s) continue;
    const label = (STAGE_LABELS[stage] ?? stage).padEnd(22);
    const status = s.status.padEnd(14);
    const dur = formatDuration(s.startedAt, s.completedAt);
    console.log(`${label}${status}${dur}`);
  }
  console.log('');
}

function resolveRunDir(runId: string, projectRoot?: string): string {
  const root = projectRoot ?? process.cwd();
  return path.join(root, '.forge', 'runs', runId);
}

function listRecentRuns(projectRoot?: string): void {
  const root = projectRoot ?? process.cwd();
  const runsDir = path.join(root, '.forge', 'runs');

  if (!fs.existsSync(runsDir)) {
    console.log('No runs found. Run `mosaic forge run` to start a pipeline.');
    return;
  }

  const entries = fs
    .readdirSync(runsDir)
    .filter((name) => fs.statSync(path.join(runsDir, name)).isDirectory())
    .sort()
    .reverse()
    .slice(0, 10);

  if (entries.length === 0) {
    console.log('No runs found.');
    return;
  }

  console.log('\nRecent runs:');
  console.log('Run ID'.padEnd(22) + 'Status'.padEnd(14) + 'Brief');
  console.log('-'.repeat(70));

  for (const runId of entries) {
    const runDir = path.join(runsDir, runId);
    try {
      const manifest = loadManifest(runDir);
      const status = manifest.status.padEnd(14);
      const brief = path.basename(manifest.brief);
      console.log(`${runId.padEnd(22)}${status}${brief}`);
    } catch {
      console.log(`${runId.padEnd(22)}${'(unreadable)'.padEnd(14)}`);
    }
  }
  console.log('');
}

// ---------------------------------------------------------------------------
// Register function
// ---------------------------------------------------------------------------

/**
 * Register forge subcommands on an existing Commander program.
 * Mirrors the pattern used by registerQualityRails in @mosaicstack/quality-rails.
 */
export function registerForgeCommand(parent: Command): void {
  const forge = parent.command('forge').description('Run and manage Forge pipelines');

  // ── forge run ────────────────────────────────────────────────────────────

  forge
    .command('run')
    .description('Run a Forge pipeline from a brief markdown file')
    .requiredOption('--brief <path>', 'Path to the brief markdown file')
    .option('--run-id <id>', 'Override the auto-generated run ID')
    .option('--resume', 'Resume an existing run instead of starting a new one', false)
    .option('--config <path>', 'Path to forge config file (.forge/config.yaml)')
    .option('--codebase <path>', 'Codebase root to pass to the pipeline', process.cwd())
    .option('--dry-run', 'Print planned stages without executing', false)
    .action(
      async (opts: {
        brief: string;
        runId?: string;
        resume: boolean;
        config?: string;
        codebase: string;
        dryRun: boolean;
      }) => {
        const briefPath = path.resolve(opts.brief);

        if (!fs.existsSync(briefPath)) {
          console.error(`[forge] brief not found: ${briefPath}`);
          process.exitCode = 1;
          return;
        }

        const briefContent = fs.readFileSync(briefPath, 'utf-8');
        const briefClass = classifyBrief(briefContent);
        const projectRoot = opts.codebase;

        if (opts.resume) {
          const runId = opts.runId ?? generateRunId();
          const runDir = resolveRunDir(runId, projectRoot);
          console.log(`[forge] resuming run: ${runId}`);
          const { resumePipeline } = await import('./pipeline-runner.js');
          const result = await resumePipeline(runDir, stubExecutor);
          console.log(`[forge] pipeline complete: ${result.runId}`);
          return;
        }

        const pipelineOptions: PipelineOptions = {
          briefClass,
          codebase: projectRoot,
          dryRun: opts.dryRun,
          executor: stubExecutor,
        };

        if (opts.dryRun) {
          const { stagesForClass } = await import('./brief-classifier.js');
          const stages = stagesForClass(briefClass);
          console.log(`[forge] dry-run — brief class: ${briefClass}`);
          console.log('[forge] planned stages:');
          for (const stage of stages) {
            console.log(`  - ${stage}  (${STAGE_LABELS[stage] ?? stage})`);
          }
          return;
        }

        console.log(`[forge] starting pipeline for brief: ${briefPath}`);
        console.log(`[forge] classified as: ${briefClass}`);

        try {
          const result = await runPipeline(briefPath, projectRoot, pipelineOptions);
          console.log(`[forge] pipeline complete: ${result.runId}`);
          console.log(`[forge] run directory: ${result.runDir}`);
        } catch (err) {
          console.error(
            `[forge] pipeline failed: ${err instanceof Error ? err.message : String(err)}`,
          );
          process.exitCode = 1;
        }
      },
    );

  // ── forge status ─────────────────────────────────────────────────────────

  forge
    .command('status [runId]')
    .description('Show the status of a pipeline run (omit runId to list recent runs)')
    .option('--project <path>', 'Project root (defaults to cwd)', process.cwd())
    .action(async (runId: string | undefined, opts: { project: string }) => {
      if (!runId) {
        listRecentRuns(opts.project);
        return;
      }

      const runDir = resolveRunDir(runId, opts.project);
      try {
        const manifest = getPipelineStatus(runDir);
        printManifestTable(manifest);
      } catch (err) {
        console.error(
          `[forge] could not load run "${runId}": ${err instanceof Error ? err.message : String(err)}`,
        );
        process.exitCode = 1;
      }
    });

  // ── forge resume ─────────────────────────────────────────────────────────

  forge
    .command('resume <runId>')
    .description('Resume a stopped or failed pipeline run')
    .option('--project <path>', 'Project root (defaults to cwd)', process.cwd())
    .action(async (runId: string, opts: { project: string }) => {
      const runDir = resolveRunDir(runId, opts.project);

      if (!fs.existsSync(runDir)) {
        console.error(`[forge] run not found: ${runDir}`);
        process.exitCode = 1;
        return;
      }

      console.log(`[forge] resuming run: ${runId}`);

      try {
        const { resumePipeline } = await import('./pipeline-runner.js');
        const result = await resumePipeline(runDir, stubExecutor);
        console.log(`[forge] pipeline complete: ${result.runId}`);
        console.log(`[forge] run directory: ${result.runDir}`);
      } catch (err) {
        console.error(`[forge] resume failed: ${err instanceof Error ? err.message : String(err)}`);
        process.exitCode = 1;
      }
    });

  // ── forge personas ────────────────────────────────────────────────────────

  const personas = forge.command('personas').description('Manage Forge board personas');

  personas
    .command('list')
    .description('List configured board personas')
    .option(
      '--project <path>',
      'Project root for persona overrides (defaults to cwd)',
      process.cwd(),
    )
    .option('--board-dir <path>', 'Override the board agents directory')
    .action((opts: { project: string; boardDir?: string }) => {
      const effectivePersonas = opts.boardDir
        ? loadBoardPersonas(opts.boardDir)
        : getEffectivePersonas(opts.project);

      if (effectivePersonas.length === 0) {
        console.log('[forge] no board personas configured.');
        return;
      }

      console.log(`\nBoard personas (${effectivePersonas.length}):\n`);
      console.log('Slug'.padEnd(24) + 'Name');
      console.log('-'.repeat(50));
      for (const p of effectivePersonas) {
        console.log(`${p.slug.padEnd(24)}${p.name}`);
      }
      console.log('');
    });
}
