/**
 * Finding and result builders for config-audit scanners.
 * Finding IDs: CA-{SCANNER}-{NNN} (e.g. CA-CML-001)
 * Zero external dependencies.
 */

import { riskScore, riskBand, verdict } from './severity.mjs';

let findingCounter = 0;

/** Reset the finding counter. Call in beforeEach of tests and before each scanner run. */
export function resetCounter() {
  findingCounter = 0;
}

/**
 * Create a finding object with auto-incremented ID.
 * @param {object} opts
 * @param {string} opts.scanner - 3-letter scanner prefix (CML, SET, HKV, RUL, etc.)
 * @param {string} opts.severity - critical | high | medium | low | info
 * @param {string} opts.title
 * @param {string} opts.description
 * @param {string} [opts.file] - file path where finding was detected
 * @param {number} [opts.line] - line number
 * @param {string} [opts.evidence] - relevant snippet
 * @param {string} [opts.category] - quality category
 * @param {string} [opts.recommendation] - suggested fix
 * @param {boolean} [opts.autoFixable] - can be auto-fixed
 * @param {object} [opts.details] - structured details (scanner-specific shape)
 * @returns {object}
 */
export function finding(opts) {
  findingCounter++;
  const id = `CA-${opts.scanner}-${String(findingCounter).padStart(3, '0')}`;
  const result = {
    id,
    scanner: opts.scanner,
    severity: opts.severity,
    title: opts.title,
    description: opts.description,
    file: opts.file || null,
    line: opts.line || null,
    evidence: opts.evidence || null,
    category: opts.category || null,
    recommendation: opts.recommendation || null,
    autoFixable: opts.autoFixable || false,
  };
  if (opts.details && typeof opts.details === 'object') {
    result.details = opts.details;
  }
  return result;
}

/**
 * Create a scanner result envelope.
 * @param {string} scannerName - 3-letter prefix
 * @param {'ok' | 'error' | 'skipped'} status
 * @param {object[]} findings
 * @param {number} filesScanned
 * @param {number} durationMs
 * @param {string} [errorMsg]
 * @returns {object}
 */
export function scannerResult(scannerName, status, findings, filesScanned, durationMs, errorMsg) {
  const counts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
  for (const f of findings) {
    if (counts[f.severity] !== undefined) {
      counts[f.severity]++;
    }
  }
  const result = {
    scanner: scannerName,
    status,
    files_scanned: filesScanned,
    duration_ms: durationMs,
    findings,
    counts,
  };
  if (errorMsg) result.error = errorMsg;
  return result;
}

/**
 * Create the top-level output envelope combining all scanner results.
 * @param {string} targetPath
 * @param {object[]} scannerResults
 * @param {number} totalDurationMs
 * @returns {object}
 */
export function envelope(targetPath, scannerResults, totalDurationMs) {
  const aggregate = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
  let totalFindings = 0;
  let scannersOk = 0;
  let scannersError = 0;
  let scannersSkipped = 0;

  for (const r of scannerResults) {
    for (const sev of Object.keys(aggregate)) {
      aggregate[sev] += (r.counts[sev] || 0);
    }
    totalFindings += r.findings.length;
    if (r.status === 'ok') scannersOk++;
    else if (r.status === 'error') scannersError++;
    else if (r.status === 'skipped') scannersSkipped++;
  }

  return {
    meta: {
      target: targetPath,
      timestamp: new Date().toISOString(),
      version: '2.2.0',
      tool: 'config-audit',
    },
    scanners: scannerResults,
    aggregate: {
      total_findings: totalFindings,
      counts: aggregate,
      risk_score: riskScore(aggregate),
      risk_band: riskBand(riskScore(aggregate)),
      verdict: verdict(aggregate),
      scanners_ok: scannersOk,
      scanners_error: scannersError,
      scanners_skipped: scannersSkipped,
    },
  };
}
