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

/**
 * Security Confidence Checker — Pre-response confidence assessment.
 *
 * Prevents wrong-direction responses by scoring confidence across
 * 5 dimensions before generating security advice. Inspired by
 * SuperClaude's ConfidenceChecker pattern, adapted for security domain.
 *
 * Confidence levels:
 *   HIGH   (≥0.90): Proceed with full response
 *   MEDIUM (0.70–0.89): Respond with explicit caveats
 *   LOW    (<0.70): Flag uncertainty, recommend verification
 *
 * @module gates/confidence
 */

// ---------------------------------------------------------------------------
// Hedging language detection
// ---------------------------------------------------------------------------

const HEDGING_PATTERNS = [
  /\bshould\s+(?:work|fix|resolve|detect|prevent|mitigate)\b/i,
  /\bprobably\s+(?:works?|fixes?|covers?|detects?)\b/i,
  /\blikely\s+sufficient\b/i,
  /\bseems?\s+to\s+(?:work|fix|detect)\b/i,
  /\bmight\s+(?:work|help|detect|prevent)\b/i,
  /\bI\s+(?:think|believe|assume)\s+(?:this|it|that)\b/i,
  /\bjust\s+this\s+once\b/i,
  /\bshould\s+be\s+(?:fine|enough|sufficient|okay)\b/i,
  /\bI'm\s+(?:fairly|pretty|quite)\s+(?:sure|confident)\b/i,
];

const RATIONALIZATION_PATTERNS = [
  { pattern: /\bshould\s+work\s+now\b/i, category: 'unverified-claim' },
  { pattern: /\bI'm\s+confident\b/i, category: 'false-confidence' },
  { pattern: /\bjust\s+this\s+once\b/i, category: 'exception-seeking' },
  { pattern: /\bpartial\s+check\s+is\s+enough\b/i, category: 'incomplete-verification' },
  { pattern: /\bno\s+need\s+to\s+(?:test|verify|check)\b/i, category: 'verification-avoidance' },
  { pattern: /\bobviously\s+(?:works?|correct|right)\b/i, category: 'assumed-correctness' },
];

// ---------------------------------------------------------------------------
// SecurityConfidenceChecker
// ---------------------------------------------------------------------------

/**
 * @typedef {Object} AssessmentContext
 * @property {string} query — The user's query
 * @property {string} mode — NERF mode (RED, BLUE, etc.)
 * @property {number} knowledgeHits — Number of knowledge base matches
 * @property {number} knowledgeRelevance — Relevance score of top match (0–1)
 * @property {'current'|'recent'|'outdated'|'unknown'} topicRecency
 * @property {boolean} hasConflictingGuidance — Whether sources conflict
 * @property {boolean} authoritativeSourceFound — Whether authoritative source exists
 */

/**
 * @typedef {Object} ConfidenceResult
 * @property {number} score — Overall confidence (0–1)
 * @property {'HIGH'|'MEDIUM'|'LOW'} level — Confidence level
 * @property {boolean} shouldProceed — Whether to generate full response
 * @property {string[]} checks — Individual check results
 * @property {string[]} concerns — Identified concerns
 * @property {string} recommendation — Action recommendation
 */

export class SecurityConfidenceChecker {
  /**
   * Assess confidence before generating a security response.
   * @param {AssessmentContext} context
   * @returns {ConfidenceResult}
   */
  assess(context) {
    let score = 0;
    const checks = [];
    const concerns = [];

    // Check 1: Knowledge base has relevant content (30%)
    if (context.knowledgeHits > 0 && context.knowledgeRelevance > 0.7) {
      score += 0.30;
      checks.push('✅ Knowledge base has relevant content');
    } else if (context.knowledgeHits > 0) {
      score += 0.15;
      checks.push('⚠️ Knowledge base has partial matches');
      concerns.push('Knowledge relevance below threshold');
    } else {
      checks.push('❌ No knowledge base matches');
      concerns.push('No authoritative knowledge for this topic');
    }

    // Check 2: Information recency (25%)
    if (context.topicRecency === 'current' || context.topicRecency === 'recent') {
      score += 0.25;
      checks.push('✅ Information is current');
    } else if (context.topicRecency === 'outdated') {
      score += 0.10;
      checks.push('⚠️ Information may be outdated');
      concerns.push('Knowledge may not reflect latest threats/mitigations');
    } else {
      checks.push('❌ Information recency unknown');
      concerns.push('Cannot verify information currency');
    }

    // Check 3: Mode-appropriate response (20%)
    if (this._isModeAligned(context)) {
      score += 0.20;
      checks.push('✅ Query aligns with active mode');
    } else {
      score += 0.10;
      checks.push('⚠️ Query may not align with active mode');
      concerns.push('Consider switching mode for better results');
    }

    // Check 4: No conflicting guidance (15%)
    if (!context.hasConflictingGuidance) {
      score += 0.15;
      checks.push('✅ No conflicting guidance detected');
    } else {
      checks.push('❌ Conflicting guidance exists');
      concerns.push('Multiple sources provide contradictory advice');
    }

    // Check 5: Authoritative source verified (10%)
    if (context.authoritativeSourceFound) {
      score += 0.10;
      checks.push('✅ Authoritative source verified');
    } else {
      checks.push('⚠️ No authoritative source found');
      concerns.push('Response based on general knowledge, not verified source');
    }

    const level = score >= 0.90 ? 'HIGH' : score >= 0.70 ? 'MEDIUM' : 'LOW';
    const shouldProceed = score >= 0.70;

    let recommendation;
    if (level === 'HIGH') {
      recommendation = 'Proceed with full response — high confidence';
    } else if (level === 'MEDIUM') {
      recommendation = 'Respond with explicit caveats — flag areas of uncertainty';
    } else {
      recommendation = 'Flag low confidence — recommend verification before acting on advice';
    }

    return { score, level, shouldProceed, checks, concerns, recommendation };
  }

  /**
   * Check if query aligns with the active NERF mode.
   * @private
   */
  _isModeAligned(context) {
    const modeTopics = {
      RED: ['exploit', 'payload', 'attack', 'bypass', 'lateral', 'privesc', 'c2'],
      BLUE: ['detect', 'detection', 'sigma', 'siem', 'hunting', 'hardening', 'edr', 'log'],
      PURPLE: ['coverage', 'emulation', 'gap', 'detection engineering'],
      PRIVACY: ['gdpr', 'ccpa', 'hipaa', 'dpia', 'anonymization', 'data flow'],
      RECON: ['osint', 'reconnaissance', 'subdomain', 'footprinting'],
      INCIDENT: ['triage', 'forensics', 'containment', 'eradication', 'timeline'],
      ARCHITECT: ['design', 'architecture', 'threat model', 'zero trust'],
      RESEARCHER: ['research', 'analysis', 'technique', 'methodology'],
    };

    const topics = modeTopics[context.mode] || [];
    if (topics.length === 0) return true; // Unknown mode — don't penalize

    const queryLower = context.query.toLowerCase();
    return topics.some(topic => queryLower.includes(topic));
  }
}

// ---------------------------------------------------------------------------
// Hedging & Rationalization Detection
// ---------------------------------------------------------------------------

/**
 * Detect hedging language in security output.
 * @param {string} text — Response text to check
 * @returns {{ hedgingFound: boolean, matches: string[], count: number }}
 */
export function detectHedging(text) {
  const matches = [];
  for (const pattern of HEDGING_PATTERNS) {
    const match = text.match(pattern);
    if (match) {
      matches.push(match[0]);
    }
  }
  return { hedgingFound: matches.length > 0, matches, count: matches.length };
}

/**
 * Detect rationalization patterns in security output.
 * @param {string} text
 * @returns {{ found: boolean, rationalizations: Array<{ text: string, category: string }> }}
 */
export function detectRationalizations(text) {
  const rationalizations = [];
  for (const { pattern, category } of RATIONALIZATION_PATTERNS) {
    const match = text.match(pattern);
    if (match) {
      rationalizations.push({ text: match[0], category });
    }
  }
  return { found: rationalizations.length > 0, rationalizations };
}

// ---------------------------------------------------------------------------
// Verification Gate
// ---------------------------------------------------------------------------

/**
 * Evidence types that satisfy the verification gate.
 */
const EVIDENCE_TYPES = {
  sigma_rule: {
    validators: [
      (text) => /^title:\s*.+/m.test(text),
      (text) => /^logsource:/m.test(text),
      (text) => /^detection:/m.test(text),
    ],
    description: 'Sigma rule must have title, logsource, and detection fields',
  },
  kql_query: {
    validators: [
      (text) => /\b(?:where|project|summarize|extend|join|let)\b/i.test(text),
    ],
    description: 'KQL query must contain valid operators',
  },
  spl_query: {
    validators: [
      (text) => /\b(?:index=|sourcetype=|search|stats|eval|table)\b/i.test(text),
    ],
    description: 'SPL query must contain valid Splunk operators',
  },
  cve_reference: {
    validators: [
      (text) => /CVE-\d{4}-\d{4,}/.test(text),
    ],
    description: 'CVE reference must be a valid CVE ID',
  },
  mitre_reference: {
    validators: [
      (text) => /T\d{4}(?:\.\d{3})?/.test(text),
    ],
    description: 'MITRE reference must be a valid technique ID',
  },
  command_output: {
    validators: [
      (text) => text.includes('$') || text.includes('#') || text.includes('>>>'),
    ],
    description: 'Command output must include shell prompt evidence',
  },
};

/**
 * Verification gate — checks that security claims have supporting evidence.
 *
 * @param {string} claim — The security claim being made
 * @param {string} evidence — Supporting evidence
 * @param {string} evidenceType — Type of evidence expected
 * @returns {{ verified: boolean, reason: string, evidenceType: string }}
 */
export function verifyEvidence(claim, evidence, evidenceType) {
  if (!evidence || evidence.trim().length === 0) {
    return { verified: false, reason: 'No evidence provided', evidenceType };
  }

  const typeSpec = EVIDENCE_TYPES[evidenceType];
  if (!typeSpec) {
    // Unknown evidence type — accept if non-empty
    return { verified: true, reason: 'Evidence provided (untyped)', evidenceType };
  }

  const failedValidators = typeSpec.validators.filter(v => !v(evidence));
  if (failedValidators.length > 0) {
    return {
      verified: false,
      reason: `Evidence does not satisfy ${evidenceType} requirements: ${typeSpec.description}`,
      evidenceType,
    };
  }

  return { verified: true, reason: `Evidence verified as valid ${evidenceType}`, evidenceType };
}

/**
 * Batch verify multiple claims against their evidence.
 * @param {Array<{ claim: string, evidence: string, evidenceType: string }>} items
 * @returns {{ allVerified: boolean, results: Array, unverifiedCount: number }}
 */
export function verifyBatch(items) {
  const results = items.map(item => ({
    claim: item.claim,
    ...verifyEvidence(item.claim, item.evidence, item.evidenceType),
  }));

  const unverifiedCount = results.filter(r => !r.verified).length;
  return { allVerified: unverifiedCount === 0, results, unverifiedCount };
}
