/**
 * Unit tests for computeEnergyShockScenario handler logic.
 *
 * Tests the pure computation functions imported from _shock-compute.ts (no Redis dependency).
 */
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';

import {
  clamp,
  computeGulfShare,
  computeEffectiveCoverDays,
  buildAssessment,
  EFFECTIVE_COVER_DAYS_CAP,
  GULF_PARTNER_CODES,
  CHOKEPOINT_EXPOSURE,
  VALID_CHOKEPOINTS,
} from '../server/worldmonitor/intelligence/v1/_shock-compute.js';

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe('energy shock scenario computation', () => {
  describe('chokepoint validation', () => {
    it('accepts all valid chokepoint IDs', () => {
      for (const id of ['hormuz_strait', 'malacca_strait', 'suez', 'bab_el_mandeb']) {
        assert.ok(VALID_CHOKEPOINTS.has(id), `Expected ${id} to be valid`);
      }
    });

    it('rejects invalid chokepoint IDs', () => {
      for (const id of ['panama', 'taiwan', '', 'xyz']) {
        assert.ok(!VALID_CHOKEPOINTS.has(id), `Expected ${id} to be invalid`);
      }
    });

    it('CHOKEPOINT_EXPOSURE contains all valid chokepoints', () => {
      for (const id of VALID_CHOKEPOINTS) {
        assert.ok(id in CHOKEPOINT_EXPOSURE, `Expected CHOKEPOINT_EXPOSURE to have key ${id}`);
      }
    });
  });

  describe('disruption_pct clamping', () => {
    it('clamps disruption_pct below 10 to 10', () => {
      assert.equal(clamp(Math.round(5), 10, 100), 10);
      assert.equal(clamp(Math.round(0), 10, 100), 10);
    });

    it('clamps disruption_pct above 100 to 100', () => {
      assert.equal(clamp(Math.round(150), 10, 100), 100);
      assert.equal(clamp(Math.round(200), 10, 100), 100);
    });

    it('passes through valid disruption_pct values unchanged', () => {
      for (const v of [10, 25, 50, 75, 100]) {
        assert.equal(clamp(v, 10, 100), v);
      }
    });
  });

  describe('gulf crude share calculation', () => {
    it('returns hasData=false when no flows provided', () => {
      const result = computeGulfShare([]);
      assert.equal(result.share, 0);
      assert.equal(result.hasData, false);
    });

    it('returns hasData=false when all flows have zero/negative tradeValueUsd', () => {
      const flows = [
        { partnerCode: '682', tradeValueUsd: 0 },
        { partnerCode: '784', tradeValueUsd: -100 },
      ];
      const result = computeGulfShare(flows);
      assert.equal(result.share, 0);
      assert.equal(result.hasData, false);
    });

    it('returns hasData=false when country has no Comtrade data (no numeric code mapping)', () => {
      const ISO2_TO_COMTRADE: Record<string, string> = {
        US: '842', CN: '156', RU: '643', IR: '364', IN: '699', TW: '490',
      };
      const unsupportedCountries = ['DE', 'FR', 'JP', 'KR', 'BR', 'SA'];
      for (const code of unsupportedCountries) {
        assert.equal(ISO2_TO_COMTRADE[code], undefined, `${code} should not have Comtrade mapping`);
      }
    });

    it('GULF_PARTNER_CODES contains expected Gulf country codes', () => {
      assert.ok(GULF_PARTNER_CODES.has('682'), 'SA should be in Gulf set');
      assert.ok(GULF_PARTNER_CODES.has('784'), 'AE should be in Gulf set');
      assert.ok(GULF_PARTNER_CODES.has('368'), 'IQ should be in Gulf set');
      assert.ok(GULF_PARTNER_CODES.has('414'), 'KW should be in Gulf set');
      assert.ok(GULF_PARTNER_CODES.has('364'), 'IR should be in Gulf set');
      assert.ok(!GULF_PARTNER_CODES.has('643'), 'RU should NOT be in Gulf set');
    });

    it('returns share=1.0 and hasData=true when all imports are from Gulf partners', () => {
      const flows = [
        { partnerCode: '682', tradeValueUsd: 1000 }, // SA
        { partnerCode: '784', tradeValueUsd: 500 },  // AE
      ];
      const result = computeGulfShare(flows);
      assert.equal(result.share, 1.0);
      assert.equal(result.hasData, true);
    });

    it('returns share=0 and hasData=true when no imports are from Gulf partners', () => {
      const flows = [
        { partnerCode: '124', tradeValueUsd: 1000 }, // Canada
        { partnerCode: '643', tradeValueUsd: 500 },  // Russia (not in Gulf set)
      ];
      const result = computeGulfShare(flows);
      assert.equal(result.share, 0);
      assert.equal(result.hasData, true);
    });

    it('computes fractional Gulf share correctly', () => {
      const flows = [
        { partnerCode: '682', tradeValueUsd: 300 }, // SA (Gulf)
        { partnerCode: '124', tradeValueUsd: 700 }, // Canada (non-Gulf)
      ];
      const result = computeGulfShare(flows);
      assert.equal(result.share, 0.3);
      assert.equal(result.hasData, true);
    });

    it('ignores flows with zero or negative tradeValueUsd', () => {
      const flows = [
        { partnerCode: '682', tradeValueUsd: 0 },   // Gulf but zero
        { partnerCode: '784', tradeValueUsd: -100 }, // Gulf but negative
        { partnerCode: '124', tradeValueUsd: 500 },  // Non-Gulf positive
      ];
      const result = computeGulfShare(flows);
      assert.equal(result.share, 0);
      assert.equal(result.hasData, true);
    });

    it('accepts numeric partnerCode values', () => {
      const flows = [
        { partnerCode: 682, tradeValueUsd: 1000 }, // SA as number
      ];
      const result = computeGulfShare(flows);
      assert.equal(result.share, 1.0);
      assert.equal(result.hasData, true);
    });
  });

  describe('effective cover days computation', () => {
    it('returns -1 for net exporters', () => {
      assert.equal(computeEffectiveCoverDays(90, true, 100, 500), -1);
    });

    it('returns raw daysOfCover when crudeLossKbd is 0', () => {
      assert.equal(computeEffectiveCoverDays(90, false, 0, 500), 90);
    });

    it('returns raw daysOfCover when crudeImportsKbd is 0', () => {
      assert.equal(computeEffectiveCoverDays(90, false, 50, 0), 90);
    });

    it('scales cover days by the loss ratio', () => {
      // 90 days cover, 50% loss of 200 kbd imports = ratio 0.5
      // effectiveCoverDays = round(90 / 0.5) = 180
      const result = computeEffectiveCoverDays(90, false, 100, 200);
      assert.equal(result, 180);
    });

    it('produces shorter cover days for higher loss ratios', () => {
      // 90 days cover, 90% disruption of 200 kbd = 180 kbd loss, ratio 0.9
      // effectiveCoverDays = round(90 / 0.9) = 100
      const result = computeEffectiveCoverDays(90, false, 180, 200);
      assert.equal(result, 100);
    });

    it('caps runaway cover days (#2971: 96 / 0.005 = 19,200 day absurdity)', () => {
      // 96 days cover, 0.5% deficit (1 kbd loss of 200 kbd imports) -> raw output 19,200
      const result = computeEffectiveCoverDays(96, false, 1, 200);
      assert.equal(result, EFFECTIVE_COVER_DAYS_CAP);
    });

    it('does NOT cap legitimate ~365-day scenarios', () => {
      // 90 days cover, 24.66% deficit (49.32 kbd loss of 200 kbd imports) -> raw 365
      const result = computeEffectiveCoverDays(90, false, 49.32, 200);
      assert.equal(result, 365);
      assert.ok(result < EFFECTIVE_COVER_DAYS_CAP, 'natural 365 should not equal the cap');
    });

    it('renders indefinitely-bridgeable prose only at or above the cap', () => {
      const msg = buildAssessment('FR', 'hormuz_strait', true, 0.5, EFFECTIVE_COVER_DAYS_CAP, 96, 25, []);
      assert.ok(msg.includes('indefinitely bridgeable'));
      assert.ok(!msg.match(/~\d{4,}\s+days/), 'should never print raw 4-digit day counts');
    });

    it('renders numeric prose for naturally-365-day scenarios (no cap regression)', () => {
      const msg = buildAssessment('FR', 'hormuz_strait', true, 0.5, 365, 90, 25, []);
      assert.ok(msg.includes('~365 days'));
      assert.ok(!msg.includes('indefinitely bridgeable'));
    });
  });

  describe('assessment string branches', () => {
    it('uses insufficient data message when dataAvailable is false', () => {
      const assessment = buildAssessment('XZ', 'suez', false, 0, 0, 0, 50, []);
      assert.ok(assessment.includes('Insufficient import data'));
      // CLDR behaviour for unrecognised codes varies across ICU versions;
      // most return the raw code, but some may resolve to "Unknown Region".
      assert.ok(assessment.includes('XZ') || assessment.includes('Unknown Region'));
      // chokepoint id is resolved to its display name ("Suez Canal")
      assert.ok(assessment.includes('Suez'));
    });

    it('uses net-exporter branch when effectiveCoverDays === -1', () => {
      const assessment = buildAssessment('SA', 'hormuz', true, 0.8, -1, 0, 50, []);
      assert.ok(assessment.includes('net oil exporter'));
    });

    it('net-exporter branch takes priority over low-Gulf-share branch', () => {
      const assessment = buildAssessment('NO', 'hormuz', true, 0.05, -1, 0, 50, []);
      assert.ok(assessment.includes('net oil exporter'));
      assert.ok(!assessment.includes('low Gulf crude dependence'));
    });

    it('uses low-dependence branch when gulfCrudeShare < 0.1', () => {
      const assessment = buildAssessment('DE', 'hormuz', true, 0.05, 180, 90, 50, []);
      assert.ok(assessment.includes('low Gulf crude dependence'));
      assert.ok(assessment.includes('5%'));
    });

    it('uses IEA cover branch when effectiveCoverDays > 90', () => {
      const assessment = buildAssessment('US', 'hormuz', true, 0.4, 180, 90, 50, []);
      assert.ok(assessment.includes('bridge'));
      assert.ok(assessment.includes('180 days'));
    });

    it('uses deficit branch when dataAvailable, gulfShare >= 0.1, effectiveCoverDays <= 90', () => {
      const products = [
        { product: 'Diesel', deficitPct: 25.0 },
        { product: 'Jet fuel', deficitPct: 20.0 },
      ];
      const assessment = buildAssessment('IN', 'malacca', true, 0.5, 60, 30, 75, products);
      assert.ok(assessment.includes('faces'));
      assert.ok(assessment.includes('25.0% diesel deficit'));
      assert.ok(assessment.includes('25.0%'));
    });

  });
});
