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

import { BaseHandler } from './base.js';

/**
 * RECON mode handler — OSINT & threat intelligence.
 * Adds: sources, indicators, profile
 */
export class ReconHandler extends BaseHandler {
  constructor() { super('recon'); }

  domainActions() {
    return ['sources', 'indicators', 'profile'];
  }

  describeDomain(action) {
    return {
      'sources':    'List OSINT sources and collection methods from technique',
      'indicators': 'Extract indicators of compromise and intelligence artifacts',
      'profile':    'Build target profile structure from technique methodology',
    }[action];
  }

  async executeDomain(command, ctx) {
    switch (command) {
      case 'sources':    return this.sources(ctx);
      case 'indicators': return this.indicators(ctx);
      case 'profile':    return this.profile(ctx);
      default: throw new Error(`Unknown RECON command: ${command}`);
    }
  }

  /** List OSINT sources and methods. */
  sources(ctx) {
    const { skill, technique } = ctx;
    if (!skill) throw new Error('No SKILL.md found');

    const sources = [];

    // Extract from tables
    for (const table of skill.tables) {
      if (table.headers.some(h => /source|tool|platform|service|api|url/i.test(h))) {
        for (const row of table.rows) {
          sources.push(row);
        }
      }
    }

    // Extract tool/API references from code blocks
    const apis = skill.codeBlocks
      .filter(b => /api|curl|request|fetch|http/i.test(b.code))
      .map(b => ({
        context: b.context,
        lang: b.lang,
        snippet: b.code.split('\n').slice(0, 5).join('\n'),
      }));

    // Classify sources as passive/active
    const passiveKeywords = /passive|public|osint|open.source|search|query|lookup/i;
    const activeKeywords = /active|scan|probe|enumerate|brute|fuzz/i;

    const collectionType = passiveKeywords.test(skill.raw) ? 'passive' :
                      activeKeywords.test(skill.raw) ? 'active' : 'mixed';

    return {
      action: 'sources',
      technique,
      mode: 'RECON',
      tools: skill.tools,
      sources: sources.slice(0, 20),
      apis: apis.slice(0, 10),
      collectionType,
      collectionClassification: this.classifyCollectionType(collectionType, skill.raw),
      status: 'sources_complete',
    };
  }

  /** Classify collection type with confidence score and OPSEC risk. */
  classifyCollectionType(type, raw) {
    const passiveKw = /passive|public|osint|open.source|search|query|lookup|whois|dns|certificate/gi;
    const activeKw = /active|scan|probe|enumerate|brute|fuzz|exploit|inject|crawl|spider/gi;
    const passiveMatches = (String(raw).match(passiveKw) || []).length;
    const activeMatches = (String(raw).match(activeKw) || []).length;
    const totalMatches = passiveMatches + activeMatches;
    const confidence = totalMatches >= 3 ? 'high' : totalMatches >= 1 ? 'medium' : 'low';
    const opsecRiskMap = { active: 'high', mixed: 'medium', passive: 'low' };
    return {
      type: type || 'mixed',
      confidence,
      opsecRisk: opsecRiskMap[type] || 'medium',
      passiveIndicators: passiveMatches,
      activeIndicators: activeMatches,
    };
  }

  /** Extract indicators and intelligence artifacts. */
  indicators(ctx) {
    const { skill, technique } = ctx;
    if (!skill) throw new Error('No SKILL.md found');

    const indicators = [];

    // Extract from tables with indicator columns
    for (const table of skill.tables) {
      if (table.headers.some(h => /indicator|ioc|artifact|hash|ip|domain|url|signature/i.test(h))) {
        for (const row of table.rows) {
          indicators.push(row);
        }
      }
    }

    // Extract patterns that look like IOCs from content
    const iocPatterns = {
      ipv4: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
      domain: /\b[\w-]+\.(?:com|net|org|io|xyz|tk|top|cc|ru)\b/gi,
      hash_md5: /\b[a-f0-9]{32}\b/gi,
      hash_sha256: /\b[a-f0-9]{64}\b/gi,
    };

    const extractedIocs = {};
    for (const [type, re] of Object.entries(iocPatterns)) {
      const matches = [...new Set(skill.raw.match(re) || [])];
      if (matches.length) extractedIocs[type] = matches.slice(0, 10);
    }

    return {
      action: 'indicators',
      technique,
      mode: 'RECON',
      attackIds: skill.attackIds,
      indicators: indicators.slice(0, 20),
      extractedIocs,
      status: 'indicators_complete',
    };
  }

  /** Build target profile structure. */
  profile(ctx) {
    const { skill, technique } = ctx;
    if (!skill) throw new Error('No SKILL.md found');

    const target = ctx.flags.target || 'unspecified';

    // Extract methodology steps
    const methodology = skill.sections
      .filter(s => /step|workflow|method|approach|procedure|phase/i.test(s.title))
      .map(s => ({
        phase: s.title,
        content: s.content.slice(0, 300),
      }));

    // Extract data points to collect
    const dataPoints = [];
    for (const table of skill.tables) {
      if (table.headers.some(h => /field|data|attribute|property|element/i.test(h))) {
        for (const row of table.rows) {
          dataPoints.push(row);
        }
      }
    }

    return {
      action: 'profile',
      technique,
      mode: 'RECON',
      target,
      tools: skill.tools,
      methodology: methodology.slice(0, 10),
      dataPoints: dataPoints.slice(0, 20),
      status: 'profile_complete',
    };
  }
}
