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

/**
 * attack-mappings.js: Runtime loader for ATT&CK and Sigma technique mappings.
 *
 * Loads the per-domain JSON files from scripts/attack-mappings/ and
 * scripts/sigma-mappings/ at runtime. Provides lookup functions for:
 *   - Which ATT&CK techniques a skill covers
 *   - Pre-built Sigma rule templates for a technique
 *   - Reverse lookup: which skills cover a given ATT&CK technique
 *
 * @module knowledge/attack-mappings
 */

import { readFileSync, readdirSync, existsSync } from 'node:fs';
import { join, dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(__dirname, '..', '..', '..');

let _attackCache = null;
let _sigmaCache = null;
let _reverseCache = null;

/**
 * Find the mappings directory. Works from both dev layout and npm install.
 * @returns {string|null}
 */
function findMappingsRoot() {
  const candidates = [
    join(REPO_ROOT, 'scripts'),            // dev: repo root
    join(__dirname, '..', '..', 'scripts'), // npm: cli/lib/knowledge -> cli/scripts
    join(REPO_ROOT, 'cli', 'scripts'),     // workspace
  ];
  for (const c of candidates) {
    if (existsSync(join(c, 'attack-mappings'))) return c;
  }
  return null;
}

/**
 * Load all attack mappings into a flat map: { "domain/technique": ["T1234", ...] }
 * @returns {Map<string, string[]>}
 */
export function loadAttackMappings() {
  if (_attackCache) return _attackCache;
  _attackCache = new Map();

  const root = findMappingsRoot();
  if (!root) return _attackCache;

  const dir = join(root, 'attack-mappings');
  for (const file of readdirSync(dir).filter(f => f.endsWith('.json'))) {
    try {
      const domain = file.replace('.json', '');
      const data = JSON.parse(readFileSync(join(dir, file), 'utf8'));
      for (const [tech, ids] of Object.entries(data)) {
        _attackCache.set(`${domain}/${tech}`, ids);
      }
    } catch { /* skip malformed files */ }
  }

  return _attackCache;
}

/**
 * Load all sigma mappings into a flat map: { "domain/technique": sigmaRuleObject }
 * @returns {Map<string, Object>}
 */
export function loadSigmaMappings() {
  if (_sigmaCache) return _sigmaCache;
  _sigmaCache = new Map();

  const root = findMappingsRoot();
  if (!root) return _sigmaCache;

  const dir = join(root, 'sigma-mappings');
  for (const file of readdirSync(dir).filter(f => f.endsWith('.json'))) {
    try {
      const domain = file.replace('.json', '');
      const data = JSON.parse(readFileSync(join(dir, file), 'utf8'));
      for (const [tech, rule] of Object.entries(data)) {
        _sigmaCache.set(`${domain}/${tech}`, rule);
      }
    } catch { /* skip malformed files */ }
  }

  return _sigmaCache;
}

/**
 * Reverse lookup: given an ATT&CK technique ID, find all skills that cover it.
 * @param {string} techniqueId - e.g. "T1059.001"
 * @returns {string[]} - array of "domain/technique" keys
 */
export function skillsForTechnique(techniqueId) {
  if (!_reverseCache) {
    _reverseCache = new Map();
    const mappings = loadAttackMappings();
    for (const [key, ids] of mappings) {
      for (const id of ids) {
        if (!_reverseCache.has(id)) _reverseCache.set(id, []);
        _reverseCache.get(id).push(key);
      }
    }
  }
  return _reverseCache.get(techniqueId) || [];
}

/**
 * Get the Sigma rule template for a technique.
 * @param {string} domain
 * @param {string} technique
 * @returns {Object|null}
 */
export function getSigmaTemplate(domain, technique) {
  const mappings = loadSigmaMappings();
  return mappings.get(`${domain}/${technique}`) || null;
}

/**
 * Get ATT&CK technique IDs for a skill.
 * @param {string} domain
 * @param {string} technique
 * @returns {string[]}
 */
export function getAttackIds(domain, technique) {
  const mappings = loadAttackMappings();
  return mappings.get(`${domain}/${technique}`) || [];
}

/**
 * Get summary stats.
 * @returns {{ attackMappings: number, sigmaMappings: number, uniqueTechniques: number }}
 */
export function getMappingStats() {
  const attack = loadAttackMappings();
  const sigma = loadSigmaMappings();
  const uniqueIds = new Set();
  for (const ids of attack.values()) {
    for (const id of ids) uniqueIds.add(id);
  }
  return {
    attackMappings: attack.size,
    sigmaMappings: sigma.size,
    uniqueTechniques: uniqueIds.size,
  };
}

/** Reset caches (for testing). */
export function _resetMappingCaches() {
  _attackCache = null;
  _sigmaCache = null;
  _reverseCache = null;
}
