import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';

const DEFAULT_SCENARIOS = [
  {
    id: 'middle-east-energy-shock',
    enabled: false,
    name: 'Middle East energy shock',
    description: 'Energy supply risk building from Middle East conflict or chokepoint pressure.',
    regions: ['Middle East', 'Iran', 'Israel', 'Strait of Hormuz'],
    categories: ['osint', 'energy', 'maritime'],
    keywords: ['missile', 'strike', 'hormuz', 'oil', 'energy', 'blockade'],
    thresholds: { watching: 2, building: 4, confirmed: 7 },
    invalidation: 'WTI normalizes and regional urgent signals fade for several sweeps.',
  },
  {
    id: 'macro-stress-spillover',
    enabled: false,
    name: 'Macro stress spillover',
    description: 'Market stress spreads from volatility into credit, rates, or commodities.',
    regions: ['US', 'Global'],
    categories: ['macro', 'markets'],
    keywords: ['vix', 'spread', 'credit', 'yield', 'inflation', 'gold'],
    thresholds: { watching: 2, building: 4, confirmed: 6 },
    invalidation: 'VIX and credit stress both normalize while source health remains stable.',
  },
  {
    id: 'regional-escalation-risk',
    enabled: false,
    name: 'Regional escalation risk',
    description: 'Local conflict signals broaden across adjacent regions or source categories.',
    regions: ['Ukraine', 'Taiwan', 'Africa', 'Middle East'],
    categories: ['conflict', 'thermal', 'osint', 'air'],
    keywords: ['mobilization', 'intercept', 'drone', 'ballistic', 'fatalities', 'border'],
    thresholds: { watching: 2, building: 5, confirmed: 8 },
    invalidation: 'No fresh cross-source escalation signals appear inside the configured horizon.',
  },
];

export function evaluateScenarios(data, delta, runsDir) {
  const loaded = loadScenarioDefinitions(runsDir);
  if (!loaded.ok) {
    return { available: false, error: loaded.error, items: [], changed: [] };
  }

  const statePath = join(runsDir, 'scenario-state.json');
  const previous = readJson(statePath, {});
  const evaluatedAt = data.meta?.timestamp || new Date().toISOString();
  const corpus = buildCorpus(data, delta);
  const items = loaded.scenarios.map(def => evaluateScenario(def, corpus, previous[def.id], evaluatedAt));
  const changed = items.filter(item => item.changed);

  writeJson(statePath, Object.fromEntries(items.map(item => [item.id, {
    state: item.state,
    score: item.score,
    confidence: item.confidence,
    lastTriggerTime: item.lastTriggerTime,
    updatedAt: evaluatedAt,
  }])));

  return {
    available: true,
    path: loaded.path,
    items,
    changed,
  };
}

export function loadScenarioDefinitions(runsDir) {
  const path = join(runsDir, 'scenarios.json');
  try {
    if (!existsSync(runsDir)) mkdirSync(runsDir, { recursive: true });
    if (!existsSync(path)) {
      writeJson(path, {
        version: 1,
        scenarios: DEFAULT_SCENARIOS,
      });
    }
    const raw = JSON.parse(readFileSync(path, 'utf8'));
    if (!raw || !Array.isArray(raw.scenarios)) throw new Error('scenarios must be an array');
    const scenarios = raw.scenarios
      .map(normalizeScenario)
      .filter(Boolean);
    return { ok: true, path, scenarios };
  } catch (err) {
    return { ok: false, path, error: err.message };
  }
}

function normalizeScenario(input) {
  if (!input || typeof input !== 'object') return null;
  const id = String(input.id || input.name || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
  const name = String(input.name || input.id || '').trim();
  if (!id || !name) return null;
  const thresholds = input.thresholds || {};
  return {
    id,
    enabled: input.enabled === true,
    name,
    description: String(input.description || ''),
    regions: arrayOfStrings(input.regions),
    categories: arrayOfStrings(input.categories),
    keywords: arrayOfStrings(input.keywords).map(s => s.toLowerCase()),
    thresholds: {
      watching: Number(thresholds.watching || 2),
      building: Number(thresholds.building || 4),
      confirmed: Number(thresholds.confirmed || 7),
    },
    invalidation: String(input.invalidation || ''),
  };
}

function evaluateScenario(def, corpus, previous, evaluatedAt) {
  if (!def.enabled) {
    return {
      ...publicScenario(def),
      state: 'dormant',
      score: 0,
      confidence: 0,
      evidence: [],
      changed: previous?.state && previous.state !== 'dormant',
      lastTriggerTime: previous?.lastTriggerTime || null,
    };
  }

  const evidence = [];
  let score = 0;
  for (const keyword of def.keywords) {
    const hit = corpus.entries.find(entry => entry.text.includes(keyword));
    if (hit) {
      score += 1;
      evidence.push({ type: 'keyword', label: keyword, source: hit.source, text: hit.original.slice(0, 180) });
    }
  }
  for (const region of def.regions) {
    const needle = region.toLowerCase();
    const hit = corpus.entries.find(entry => entry.text.includes(needle));
    if (hit) {
      score += 1;
      evidence.push({ type: 'region', label: region, source: hit.source, text: hit.original.slice(0, 180) });
    }
  }
  for (const category of def.categories) {
    if (corpus.categories.has(category.toLowerCase())) {
      score += 1;
      evidence.push({ type: 'category', label: category, source: 'sweep', text: `${category} category active` });
    }
  }

  const state = score >= def.thresholds.confirmed ? 'confirmed'
    : score >= def.thresholds.building ? 'building'
      : score >= def.thresholds.watching ? 'watching'
        : 'dormant';
  const confidence = Math.min(100, Math.round((score / Math.max(1, def.thresholds.confirmed)) * 100));
  const changed = previous?.state ? previous.state !== state : state !== 'dormant';
  return {
    ...publicScenario(def),
    state,
    score,
    confidence,
    evidence: evidence.slice(0, 6),
    changed,
    lastTriggerTime: state === 'dormant' ? (previous?.lastTriggerTime || null) : evaluatedAt,
  };
}

function publicScenario(def) {
  return {
    id: def.id,
    name: def.name,
    description: def.description,
    enabled: def.enabled,
    invalidation: def.invalidation,
  };
}

function buildCorpus(data, delta) {
  const entries = [];
  const categories = new Set();
  const push = (source, text, category) => {
    if (!text) return;
    entries.push({ source, original: String(text), text: String(text).toLowerCase() });
    if (category) categories.add(category);
  };

  for (const signal of data.tSignals || []) push('thermal', signal, 'thermal');
  for (const post of data.tg?.urgent || []) push(post.channel || 'telegram', post.text, 'osint');
  for (const item of data.newsFeed || []) push(item.source || 'news', item.headline || item.title, 'news');
  for (const item of data.news || []) push(item.source || 'news', item.headline || item.title, 'news');
  for (const item of data.acled?.deadliestEvents || []) push('ACLED', `${item.country || ''} ${item.location || ''} ${item.event_type || ''} ${item.fatalities || ''}`, 'conflict');
  for (const item of data.air || []) push('OpenSky', `${item.region} ${item.total} aircraft`, 'air');
  for (const item of data.chokepoints || []) push('Maritime', `${item.label} ${item.note}`, 'maritime');
  if (data.energy?.wti || data.energy?.brent) push('energy', `WTI ${data.energy.wti} Brent ${data.energy.brent}`, 'energy');
  if (data.markets?.vix || data.fred?.some(f => f.id === 'VIXCLS')) push('markets', 'VIX volatility market stress', 'markets');
  if (delta?.summary) push('delta', `${delta.summary.direction} ${delta.summary.totalChanges} changes ${delta.summary.criticalChanges} critical`, 'delta');
  for (const signal of delta?.signals?.new || []) push('delta', signal.label || signal.reason || signal.key, 'delta');
  for (const signal of delta?.signals?.escalated || []) push('delta', signal.label || signal.reason || signal.key, 'delta');

  return { entries, categories };
}

function arrayOfStrings(value) {
  return Array.isArray(value) ? value.map(v => String(v).trim()).filter(Boolean) : [];
}

function readJson(path, fallback) {
  try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return fallback; }
}

function writeJson(path, value) {
  writeFileSync(path, JSON.stringify(value, null, 2));
}
