// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 — see LICENSE file for details.
// NERF is a trademark of defconxt.

/**
 * NERF Two-Stage Review Pipeline
 *
 * Stage 1 — Spec Compliance: security requirements gate (auth, validation, error handling)
 * Stage 2 — Quality Gate: code quality patterns (only runs if Stage 1 passes threshold)
 *
 * Uses the review engine from M011 with hookable stages from M013.
 *
 * @module review/two-stage
 */

import { createReviewEngine } from './engine.js';
import { PipelineHooks, HookContext, hookableStage } from '../pipeline/hooks.js';

// ---------------------------------------------------------------------------
// Severity thresholds
// ---------------------------------------------------------------------------

const SEVERITY_RANK = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };

/**
 * @typedef {object} StageConfig
 * @property {string} [minSeverity] - Filter findings at or above this level
 * @property {number} [maxCritical] - Max critical findings before stage fails
 * @property {number} [maxHigh]     - Max high findings before stage fails
 */

/** Default stage configs */
const STAGE_DEFAULTS = {
  compliance: {
    minSeverity: 'medium',
    maxCritical: 0,
    maxHigh: 3,
  },
  quality: {
    minSeverity: 'low',
    maxCritical: 0,
    maxHigh: 5,
  },
};

// ---------------------------------------------------------------------------
// Two-Stage Review
// ---------------------------------------------------------------------------

/**
 * Two-stage review pipeline result.
 */
export class TwoStageResult {
  constructor({ stage1 = null, stage2 = null, gatesPassed = false, abortReason = '', totalTime = 0 } = {}) {
    this.stage1 = stage1;
    this.stage2 = stage2;
    this.gatesPassed = gatesPassed;
    this.abortReason = abortReason;
    this.totalTime = totalTime;
  }

  get allFindings() {
    const findings = [];
    if (this.stage1?.findings) findings.push(...this.stage1.findings);
    if (this.stage2?.findings) findings.push(...this.stage2.findings);
    return findings;
  }

  toReport() {
    const lines = [
      '═══════════════════════════════════════════════════════',
      '  NERF Two-Stage Review Report',
      '═══════════════════════════════════════════════════════',
      '',
    ];

    // Stage 1
    lines.push('── Stage 1: Spec Compliance ──');
    if (this.stage1) {
      lines.push(`Result: ${this.stage1.passed ? '✓ PASSED' : '✗ FAILED'}`);
      lines.push(this.stage1.result?.summary ?? 'No results');
      if (!this.stage1.passed && this.stage1.failReason) {
        lines.push(`Reason: ${this.stage1.failReason}`);
      }
    } else {
      lines.push('Not executed');
    }
    lines.push('');

    // Stage 2
    lines.push('── Stage 2: Quality Gate ──');
    if (this.stage2) {
      lines.push(`Result: ${this.stage2.passed ? '✓ PASSED' : '✗ FAILED'}`);
      lines.push(this.stage2.result?.summary ?? 'No results');
      if (!this.stage2.passed && this.stage2.failReason) {
        lines.push(`Reason: ${this.stage2.failReason}`);
      }
    } else {
      lines.push(this.abortReason ? `Skipped: ${this.abortReason}` : 'Not executed');
    }
    lines.push('');

    // Overall
    lines.push('───────────────────────────────────────────────────────');
    lines.push(`Overall: ${this.gatesPassed ? '✓ ALL GATES PASSED' : '✗ GATES FAILED'}`);
    lines.push(`Total time: ${this.totalTime}ms`);
    lines.push(`Total findings: ${this.allFindings.length}`);

    return lines.join('\n');
  }

  toJSON() {
    return {
      gatesPassed: this.gatesPassed,
      abortReason: this.abortReason,
      totalTime: this.totalTime,
      totalFindings: this.allFindings.length,
      stage1: this.stage1 ? {
        passed: this.stage1.passed,
        failReason: this.stage1.failReason,
        findings: this.stage1.result?.findings?.length ?? 0,
        summary: this.stage1.result?.summary ?? '',
      } : null,
      stage2: this.stage2 ? {
        passed: this.stage2.passed,
        failReason: this.stage2.failReason,
        findings: this.stage2.result?.findings?.length ?? 0,
        summary: this.stage2.result?.summary ?? '',
      } : null,
    };
  }
}

/**
 * Run a two-stage code review.
 *
 * @param {string} input          - File path, directory, or code string
 * @param {object} [options]
 * @param {StageConfig} [options.compliance] - Stage 1 config
 * @param {StageConfig} [options.quality]    - Stage 2 config
 * @param {PipelineHooks} [options.hooks]    - Pipeline hooks
 * @param {string} [options.language]        - Override language detection
 * @returns {Promise<TwoStageResult>}
 */
export async function twoStageReview(input, options = {}) {
  const t0 = Date.now();
  const complianceCfg = { ...STAGE_DEFAULTS.compliance, ...options.compliance };
  const qualityCfg = { ...STAGE_DEFAULTS.quality, ...options.quality };
  const hooks = options.hooks ?? new PipelineHooks();

  const engine = await createReviewEngine();

  // Stage 1: Spec Compliance
  const stage1Fn = async (target, opts) => {
    return engine.review(target, {
      language: options.language,
      minSeverity: complianceCfg.minSeverity,
    });
  };

  const hookedStage1 = hookableStage(hooks, 'review', stage1Fn);
  let stage1Result;
  let stage1Passed = true;
  let stage1FailReason = '';

  try {
    stage1Result = await hookedStage1(input, { stage: 'compliance' });

    // Check thresholds
    const counts = stage1Result.severityCounts;
    if (complianceCfg.maxCritical !== undefined && counts.critical > complianceCfg.maxCritical) {
      stage1Passed = false;
      stage1FailReason = `${counts.critical} critical findings exceed threshold of ${complianceCfg.maxCritical}`;
    }
    if (stage1Passed && complianceCfg.maxHigh !== undefined && counts.high > complianceCfg.maxHigh) {
      stage1Passed = false;
      stage1FailReason = `${counts.high} high findings exceed threshold of ${complianceCfg.maxHigh}`;
    }
  } catch (err) {
    stage1Passed = false;
    stage1FailReason = err.message;
  }

  const stage1 = {
    passed: stage1Passed,
    failReason: stage1FailReason,
    result: stage1Result ?? null,
  };

  // Stage 2: Quality Gate — only if Stage 1 passed
  let stage2 = null;
  let abortReason = '';

  if (stage1Passed) {
    const stage2Fn = async (target, opts) => {
      return engine.review(target, {
        language: options.language,
        minSeverity: qualityCfg.minSeverity,
      });
    };

    const hookedStage2 = hookableStage(hooks, 'analyze', stage2Fn);
    let stage2Passed = true;
    let stage2FailReason = '';

    try {
      const stage2Result = await hookedStage2(input, { stage: 'quality' });

      const counts = stage2Result.severityCounts;
      if (qualityCfg.maxCritical !== undefined && counts.critical > qualityCfg.maxCritical) {
        stage2Passed = false;
        stage2FailReason = `${counts.critical} critical findings exceed threshold of ${qualityCfg.maxCritical}`;
      }
      if (stage2Passed && qualityCfg.maxHigh !== undefined && counts.high > qualityCfg.maxHigh) {
        stage2Passed = false;
        stage2FailReason = `${counts.high} high findings exceed threshold of ${qualityCfg.maxHigh}`;
      }

      stage2 = {
        passed: stage2Passed,
        failReason: stage2FailReason,
        result: stage2Result,
      };
    } catch (err) {
      stage2 = {
        passed: false,
        failReason: err.message,
        result: null,
      };
    }
  } else {
    abortReason = `Stage 1 failed: ${stage1FailReason}`;
  }

  const gatesPassed = stage1Passed && (stage2?.passed ?? false);

  return new TwoStageResult({
    stage1,
    stage2,
    gatesPassed,
    abortReason,
    totalTime: Date.now() - t0,
  });
}
