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

/**
 * OSINT intelligence report validator for RECON mode agent output.
 *
 * Ported from execution-engine/validators/osint.py.
 * Validates LLM-generated JSON intelligence reports for structural correctness:
 * required sections, non-empty data, and finding structure validation.
 *
 * @module execution-engine/validators/osint
 */

import { ValidationResult } from '../framework.js';

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const REQUIRED_SECTIONS = [
  'summary', 'target', 'dns_records', 'whois_data', 'technologies', 'findings',
];

const RECOMMENDED_TOP_LEVEL_FIELDS = [
  'subdomains', 'infrastructure', 'collection_metadata',
];

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/**
 * Extract JSON content from markdown code fences, or return text as-is.
 *
 * @param {string} text
 * @returns {string}
 */
function stripCodeFences(text) {
  // Try explicit json-tagged fences first
  let matches = [...text.matchAll(/```json\s*\n(.*?)```/gs)].map(m => m[1]);
  if (matches.length > 0) return matches.join('\n');

  // Fall back to bare fences
  matches = [...text.matchAll(/```\s*\n(.*?)```/gs)].map(m => m[1]);
  if (matches.length > 0) return matches.join('\n');

  return text;
}

/**
 * Parse a JSON OSINT report from text.
 *
 * @param {string} text
 * @returns {Object}
 * @throws {Error}
 */
function parseJsonReport(text) {
  const stripped = stripCodeFences(text);
  try {
    return JSON.parse(stripped);
  } catch (e) {
    throw new Error(`JSON parse error: ${e.message}`);
  }
}

/**
 * Validate an OSINT report dict for structural correctness.
 *
 * @param {Object} report
 * @returns {{ errors: string[], warnings: string[] }}
 */
function validateReport(report) {
  const errors = [];
  const warnings = [];

  // Required top-level sections
  for (const section of REQUIRED_SECTIONS) {
    if (!(section in report)) {
      errors.push(`Missing required section: ${section}`);
    }
  }

  // findings must be a non-empty list
  const findings = report.findings;
  if (findings !== undefined) {
    if (!Array.isArray(findings)) {
      errors.push(`findings must be a list (got ${typeof findings})`);
    } else if (findings.length === 0) {
      errors.push('findings list must not be empty');
    }
  }

  // dns_records must be a non-empty dict or list
  const dnsRecords = report.dns_records;
  if (dnsRecords !== undefined) {
    if (typeof dnsRecords === 'object' && dnsRecords !== null && !Array.isArray(dnsRecords)) {
      if (Object.keys(dnsRecords).length === 0) {
        errors.push('dns_records must not be empty');
      }
    } else if (Array.isArray(dnsRecords)) {
      if (dnsRecords.length === 0) {
        errors.push('dns_records must not be empty');
      }
    } else {
      errors.push(`dns_records must be a dict or list (got ${typeof dnsRecords})`);
    }
  }

  // Recommended top-level fields
  for (const fieldName of RECOMMENDED_TOP_LEVEL_FIELDS) {
    if (!(fieldName in report)) {
      warnings.push(`Missing recommended field: ${fieldName}`);
    }
  }

  return { errors, warnings };
}

// ---------------------------------------------------------------------------
// OSINTValidator
// ---------------------------------------------------------------------------

/**
 * Deterministic validator for JSON OSINT intelligence reports.
 *
 * Scoring:
 *   - 1.0: no errors and no warnings (clean)
 *   - 0.5: warnings only (valid but imperfect)
 *   - 0.0: any errors (invalid)
 */
export class OSINTValidator {
  /**
   * @param {import('../framework.js').ModeAgentResult} result
   * @returns {import('../framework.js').ValidationResult}
   */
  validate(result) {
    const text = result.outputText || '';
    const emptyMeta = { findings_count: 0, technologies_count: 0, dns_record_types: [] };

    // Empty output
    if (!text.trim()) {
      return new ValidationResult({
        valid: false,
        errors: ['No OSINT report found in output (empty)'],
        warnings: [],
        score: 0.0,
        metadata: { ...emptyMeta },
      });
    }

    // Parse JSON
    let parsed;
    try {
      parsed = parseJsonReport(text);
    } catch (e) {
      return new ValidationResult({
        valid: false,
        errors: [e.message],
        warnings: [],
        score: 0.0,
        metadata: { ...emptyMeta },
      });
    }

    // Must be an object
    if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
      return new ValidationResult({
        valid: false,
        errors: [`OSINT report must be a JSON object (got ${Array.isArray(parsed) ? 'Array' : typeof parsed})`],
        warnings: [],
        score: 0.0,
        metadata: { ...emptyMeta },
      });
    }

    // Structural validation
    const { errors, warnings } = validateReport(parsed);

    // Compute metadata
    const findings = parsed.findings || [];
    const technologies = parsed.technologies || [];
    const dnsRecords = parsed.dns_records || {};

    const findingsCount = Array.isArray(findings) ? findings.length : 0;
    const technologiesCount = Array.isArray(technologies) ? technologies.length
      : (typeof technologies === 'object' && technologies !== null ? Object.keys(technologies).length : 0);

    let dnsRecordTypes = [];
    if (typeof dnsRecords === 'object' && dnsRecords !== null && !Array.isArray(dnsRecords)) {
      dnsRecordTypes = Object.keys(dnsRecords).sort();
    }

    // Scoring
    let score;
    if (errors.length > 0) {
      score = 0.0;
    } else if (warnings.length > 0) {
      score = 0.5;
    } else {
      score = 1.0;
    }

    return new ValidationResult({
      valid: errors.length === 0,
      errors,
      warnings,
      score,
      metadata: {
        findings_count: findingsCount,
        technologies_count: technologiesCount,
        dns_record_types: dnsRecordTypes,
      },
    });
  }
}
