import { spawnSync } from 'node:child_process';
import { appendFileSync, mkdirSync } from 'node:fs';
import { dirname } from 'node:path';

import { emitEvent } from './event-emitter.js';
import { nowISO } from './event-emitter.js';
import type { GateResult } from './types.js';

export interface NormalizedGate {
  command: string;
  type: string;
  fail_on: string;
}

export function normalizeGate(gate: unknown): NormalizedGate {
  if (typeof gate === 'string') {
    return { command: gate, type: 'mechanical', fail_on: 'blocker' };
  }
  if (typeof gate === 'object' && gate !== null && !Array.isArray(gate)) {
    const g = gate as Record<string, unknown>;
    return {
      command: String(g['command'] ?? ''),
      type: String(g['type'] ?? 'mechanical'),
      fail_on: String(g['fail_on'] ?? 'blocker'),
    };
  }
  return { command: '', type: 'mechanical', fail_on: 'blocker' };
}

export function runShell(
  command: string,
  cwd: string,
  logPath: string,
  timeoutSec: number,
): { exitCode: number; output: string; timedOut: boolean } {
  mkdirSync(dirname(logPath), { recursive: true });

  const header = `\n[${nowISO()}] COMMAND: ${command}\n`;
  appendFileSync(logPath, header, 'utf-8');

  let exitCode: number;
  let output = '';
  let timedOut = false;

  try {
    const result = spawnSync('sh', ['-c', command], {
      cwd,
      timeout: Math.max(1, timeoutSec) * 1000,
      encoding: 'utf-8',
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    output = (result.stdout ?? '') + (result.stderr ?? '');

    if (result.error && (result.error as NodeJS.ErrnoException).code === 'ETIMEDOUT') {
      timedOut = true;
      exitCode = 124;
      appendFileSync(logPath, `[${nowISO()}] TIMEOUT: exceeded ${timeoutSec}s\n`, 'utf-8');
    } else {
      exitCode = result.status ?? 1;
    }
  } catch {
    exitCode = 1;
  }

  if (output) appendFileSync(logPath, output, 'utf-8');
  appendFileSync(logPath, `[${nowISO()}] EXIT: ${exitCode}\n`, 'utf-8');

  return { exitCode, output, timedOut };
}

export function countAIFindings(parsedOutput: unknown): { blockers: number; total: number } {
  if (typeof parsedOutput !== 'object' || parsedOutput === null || Array.isArray(parsedOutput)) {
    return { blockers: 0, total: 0 };
  }

  const obj = parsedOutput as Record<string, unknown>;
  const stats = obj['stats'];
  let blockers = 0;
  let total = 0;

  if (typeof stats === 'object' && stats !== null && !Array.isArray(stats)) {
    const s = stats as Record<string, unknown>;
    blockers = Number(s['blockers']) || 0;
    total = blockers + (Number(s['should_fix']) || 0) + (Number(s['suggestions']) || 0);
  }

  const findings = obj['findings'];
  if (Array.isArray(findings)) {
    if (blockers === 0) {
      blockers = findings.filter(
        (f) =>
          typeof f === 'object' &&
          f !== null &&
          (f as Record<string, unknown>)['severity'] === 'blocker',
      ).length;
    }
    if (total === 0) {
      total = findings.length;
    }
  }

  return { blockers, total };
}

export function runGate(
  gate: unknown,
  cwd: string,
  logPath: string,
  timeoutSec: number,
): GateResult {
  const gateEntry = normalizeGate(gate);
  const gateType = gateEntry.type;
  const command = gateEntry.command;

  if (gateType === 'ci-pipeline') {
    return {
      command,
      exit_code: 0,
      type: gateType,
      output: 'CI pipeline gate placeholder',
      timed_out: false,
      passed: true,
    };
  }

  if (!command) {
    return {
      command: '',
      exit_code: 0,
      type: gateType,
      output: '',
      timed_out: false,
      passed: true,
    };
  }

  const { exitCode, output, timedOut } = runShell(command, cwd, logPath, timeoutSec);
  const result: GateResult = {
    command,
    exit_code: exitCode,
    type: gateType,
    output,
    timed_out: timedOut,
    passed: false,
  };

  if (gateType !== 'ai-review') {
    result.passed = exitCode === 0;
    return result;
  }

  const failOn = gateEntry.fail_on || 'blocker';
  let parsedOutput: unknown = undefined;
  let blockers = 0;
  let findingsCount = 0;
  let parseError: string | undefined;

  try {
    parsedOutput = output.trim() ? JSON.parse(output) : {};
    const counts = countAIFindings(parsedOutput);
    blockers = counts.blockers;
    findingsCount = counts.total;
  } catch (exc) {
    parseError = String(exc instanceof Error ? exc.message : exc);
  }

  if (failOn === 'any') {
    result.passed = exitCode === 0 && findingsCount === 0 && !timedOut && parseError === undefined;
  } else {
    result.passed = exitCode === 0 && blockers === 0 && !timedOut && parseError === undefined;
  }

  result.fail_on = failOn;
  result.blockers = blockers;
  result.findings = findingsCount;
  if (parsedOutput !== undefined) {
    result.parsed_output = parsedOutput;
  }
  if (parseError !== undefined) {
    result.parse_error = parseError;
  }

  return result;
}

export function runGates(
  gates: unknown[],
  cwd: string,
  logPath: string,
  timeoutSec: number,
  eventsPath: string,
  taskId: string,
): { allPassed: boolean; gateResults: GateResult[] } {
  let allPassed = true;
  const gateResults: GateResult[] = [];

  for (const gate of gates) {
    const gateEntry = normalizeGate(gate);
    const gateCmd = gateEntry.command;
    if (!gateCmd && gateEntry.type !== 'ci-pipeline') continue;

    const label = gateCmd || gateEntry.type;
    emitEvent(
      eventsPath,
      'rail.check.started',
      taskId,
      'gated',
      'quality-gate',
      `Running gate: ${label}`,
    );
    const result = runGate(gate, cwd, logPath, timeoutSec);
    gateResults.push(result);

    if (result.passed) {
      emitEvent(
        eventsPath,
        'rail.check.passed',
        taskId,
        'gated',
        'quality-gate',
        `Gate passed: ${label}`,
      );
      continue;
    }

    allPassed = false;
    let message: string;
    if (result.timed_out) {
      message = `Gate timed out after ${timeoutSec}s: ${label}`;
    } else if (result.type === 'ai-review' && result.parse_error) {
      message = `AI review gate output was not valid JSON: ${label}`;
    } else {
      message = `Gate failed (${result.exit_code}): ${label}`;
    }
    emitEvent(eventsPath, 'rail.check.failed', taskId, 'gated', 'quality-gate', message);
  }

  return { allPassed, gateResults };
}
