import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import { Command } from 'commander';

import { detectProjectKind } from './detect.js';
import { scaffoldQualityRails } from './scaffolder.js';
import type { ProjectKind, QualityProfile, RailsConfig } from './types.js';
import { QC_19_RAILS_FILES_PRESENT } from './evaluator/definitions.js';
import { evaluateSubject } from './evaluator/runner.js';
import type { EvaluationReport } from './evaluator/types.js';

const VALID_PROFILES: readonly QualityProfile[] = ['strict', 'standard', 'minimal'];

function parseProfile(rawProfile: string): QualityProfile {
  if (VALID_PROFILES.includes(rawProfile as QualityProfile)) {
    return rawProfile as QualityProfile;
  }
  throw new Error(`Invalid profile: ${rawProfile}. Use one of ${VALID_PROFILES.join(', ')}.`);
}

function defaultLinters(kind: ProjectKind): string[] {
  if (kind === 'node') {
    return ['eslint', 'biome'];
  }

  if (kind === 'python') {
    return ['ruff'];
  }

  if (kind === 'rust') {
    return ['clippy'];
  }

  return [];
}

function defaultFormatters(kind: ProjectKind): string[] {
  if (kind === 'node') {
    return ['prettier'];
  }

  if (kind === 'python') {
    return ['black'];
  }

  if (kind === 'rust') {
    return ['rustfmt'];
  }

  return [];
}

function printScaffoldResult(
  config: RailsConfig,
  filesWritten: string[],
  warnings: string[],
  commandsToRun: string[],
): void {
  console.log(`[quality-rails] initialized at ${config.projectPath}`);
  console.log(`kind=${config.kind} profile=${config.profile}`);

  if (filesWritten.length > 0) {
    console.log('files written:');
    for (const filePath of filesWritten) {
      console.log(`  - ${filePath}`);
    }
  }

  if (commandsToRun.length > 0) {
    console.log('run next:');
    for (const command of commandsToRun) {
      console.log(`  - ${command}`);
    }
  }

  if (warnings.length > 0) {
    console.log('warnings:');
    for (const warning of warnings) {
      console.log(`  - ${warning}`);
    }
  }
}

function printEvaluationReport(report: EvaluationReport): void {
  console.log(
    `[quality-rails] evaluation for ${report.subject.path} (kind=${report.subject.kind}, check-set v${report.checkSetVersion})`,
  );
  for (const result of report.results) {
    const reason = result.reason === undefined ? '' : ` — ${result.reason}`;
    const digest = report.definitionDigests[result.checkId] ?? 'no digest';
    console.log(
      `  - ${result.status}: ${result.checkId} (v${result.checkVersion} [${digest}])${reason}`,
    );
  }
  console.log(`[quality-rails] aggregate: ${report.state}`);
}

/**
 * Register quality-rails subcommands on an existing Commander program.
 * This avoids cross-package Commander version mismatches by using the
 * caller's Command instance directly.
 */
export function registerQualityRails(parent: Command): void {
  buildQualityRailsCommand(
    parent.command('quality-rails').description('Manage quality rails scaffolding'),
  );
}

export function createQualityRailsCli(): Command {
  const program = new Command('mosaic');
  buildQualityRailsCommand(
    program.command('quality-rails').description('Manage quality rails scaffolding'),
  );
  return program;
}

function buildQualityRailsCommand(qualityRails: Command): void {
  qualityRails
    .command('init')
    .requiredOption('--project <path>', 'Project path')
    .option('--profile <profile>', 'strict|standard|minimal', 'standard')
    .action(async (options: { project: string; profile: string }) => {
      const profile = parseProfile(options.profile);
      const projectPath = resolve(options.project);
      const kind = await detectProjectKind(projectPath);

      const config: RailsConfig = {
        projectPath,
        kind,
        profile,
        linters: defaultLinters(kind),
        formatters: defaultFormatters(kind),
        hooks: true,
      };

      const result = await scaffoldQualityRails(config);
      printScaffoldResult(config, result.filesWritten, result.warnings, result.commandsToRun);
    });

  // `check` (QC-19) is ABSORBED by the RI-N4 evaluator: the presence loop
  // that lived here is now the versioned, digested, typed check definition
  // `qc-19-rails-files-present`. The CLI keeps its human surface (missing
  // files listed, exit 1) and gains `--json` for the typed verdicts. Exit
  // code is fail-closed: any non-green aggregate (failed/blocked/error) is 1.
  qualityRails
    .command('check')
    .requiredOption('--project <path>', 'Project path')
    .option('--json', 'print the typed evaluation report as JSON')
    .action(async (options: { project: string; json?: boolean }) => {
      const projectPath = resolve(options.project);
      const report = await evaluateSubject({
        subjectPath: projectPath,
        checkIds: [QC_19_RAILS_FILES_PRESENT.id],
      });

      if (options.json) {
        console.log(JSON.stringify(report));
      } else {
        printEvaluationReport(report);
      }

      process.exitCode = report.state === 'passed' ? 0 : 1;
    });

  // `doctor` (QC-19) stays advisory (documented contract: a doctor that
  // cannot fail), but now reports TYPED states — a blocked or failing rail is
  // visible instead of silently printed as `ok`/`missing`.
  qualityRails
    .command('doctor')
    .requiredOption('--project <path>', 'Project path')
    .action(async (options: { project: string }) => {
      const projectPath = resolve(options.project);
      const report = await evaluateSubject({ subjectPath: projectPath });

      console.log(`[quality-rails] doctor for ${projectPath}`);
      console.log(`detected project kind: ${report.subject.kind}`);
      for (const result of report.results) {
        const reason = result.reason === undefined ? '' : ` — ${result.reason}`;
        console.log(`  - ${result.status}: ${result.checkId}${reason}`);
      }

      if (report.subject.kind === 'unknown') {
        console.log(
          'recommendation: add package.json, pyproject.toml, or Cargo.toml for better defaults.',
        );
      }
    });

  // `evaluate` is the canonical RI-N4 evaluator entry point: typed verdicts
  // for the subject's full per-kind check set, same results as the
  // programmatic API (evaluateSubject).
  qualityRails
    .command('evaluate')
    .description('Run the typed quality-rails evaluator against a subject project')
    .requiredOption('--project <path>', 'Project path')
    .option('--check <id...>', 'restrict evaluation to these check ids')
    .option(
      '--probe-path <path>',
      'path to the QC-20 behavioral probe script (framework verify.sh)',
    )
    .option('--json', 'print the typed evaluation report as JSON')
    .action(
      async (options: {
        project: string;
        check?: string[];
        probePath?: string;
        json?: boolean;
      }) => {
        const projectPath = resolve(options.project);
        const report = await evaluateSubject({
          subjectPath: projectPath,
          checkIds: options.check,
          inputs: options.probePath
            ? { 'qc-20-enforcement-verify': { probePath: options.probePath } }
            : undefined,
        });

        if (options.json) {
          console.log(JSON.stringify(report));
        } else {
          printEvaluationReport(report);
        }

        process.exitCode = report.state === 'passed' ? 0 : 1;
      },
    );
}

export async function runQualityRailsCli(argv: string[] = process.argv): Promise<void> {
  const program = createQualityRailsCli();
  await program.parseAsync(argv);
}

const entryPath = process.argv[1] ? resolve(process.argv[1]) : '';
if (entryPath.length > 0 && entryPath === fileURLToPath(import.meta.url)) {
  runQualityRailsCli().catch((error: unknown) => {
    console.error(error instanceof Error ? error.message : String(error));
    process.exit(1);
  });
}
