import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import { OWAFusion } from '../../src/utils/OWAFusion.js';

function createRng(seed) {
  let state = seed >>> 0;
  return () => {
    state = (1664525 * state + 1013904223) >>> 0;
    return state / 0x100000000;
  };
}

function randInt(rng, max) {
  return Math.floor(rng() * max);
}

function randFloat(rng, min = 0, max = 1) {
  return min + (max - min) * rng();
}

function approxEqual(a, b, eps = 1e-6) {
  return Math.abs(a - b) <= eps;
}

describe('OWA aggregation properties', () => {
  test('bounded and translation properties across modes', () => {
    const rng = createRng(42);
    const modes = ['max', 'min', 'average', 'majority', 'median', 'optimistic', 'pessimistic', 'top2', 'top3'];
    const iterations = 200;

    for (let i = 0; i < iterations; i++) {
      const length = randInt(rng, 8) + 1;
      const values = new Array(length).fill(0).map(() => randFloat(rng, -50, 50));
      const metas = new Array(length).fill(null);
      const min = Math.min(...values);
      const max = Math.max(...values);

      for (const mode of modes) {
        const weights = OWAFusion.generateOWAWeights(length, mode, null, true);
        const result = OWAFusion.fuseWithMeta(values, metas, weights, mode, true).value;
        assert.ok(result >= min - 1e-6 && result <= max + 1e-6, `mode ${mode} bounds`);

        const weightSum = weights.reduce((sum, w) => sum + w, 0);
        if (approxEqual(weightSum, 1.0)) {
          const delta = randFloat(rng, -10, 10);
          const shifted = values.map(v => v + delta);
          const shiftedResult = OWAFusion.fuseWithMeta(shifted, metas, weights, mode, true).value;
          assert.ok(approxEqual(shiftedResult - result, delta, 1e-5), `mode ${mode} translation`);
        }
      }
    }
  });

  test('extreme strategies behave as expected', () => {
    const rng = createRng(7);
    for (let i = 0; i < 200; i++) {
      const length = randInt(rng, 8) + 1;
      const values = new Array(length).fill(0).map(() => randFloat(rng, -20, 20));
      const metas = new Array(length).fill(null);
      const max = Math.max(...values);
      const min = Math.min(...values);

      const maxWeights = OWAFusion.generateOWAWeights(length, 'max', null, true);
      const minWeights = OWAFusion.generateOWAWeights(length, 'min', null, true);
      const maxResult = OWAFusion.fuseWithMeta(values, metas, maxWeights, 'max', true).value;
      const minResult = OWAFusion.fuseWithMeta(values, metas, minWeights, 'min', true).value;
      assert.ok(approxEqual(maxResult, max, 1e-6), 'max aggregator');
      assert.ok(approxEqual(minResult, min, 1e-6), 'min aggregator');
    }
  });

  test('custom weights respect convex combination', () => {
    const rng = createRng(13);
    for (let i = 0; i < 200; i++) {
      const length = randInt(rng, 8) + 1;
      const values = new Array(length).fill(0).map(() => randFloat(rng, -100, 100));
      const metas = new Array(length).fill(null);

      let weights = new Array(length).fill(0).map(() => randFloat(rng, 0, 1));
      const sum = weights.reduce((a, b) => a + b, 0) || 1;
      weights = weights.map(w => w / sum);

      const min = Math.min(...values);
      const max = Math.max(...values);
      const result = OWAFusion.fuseWithMeta(values, metas, weights, 'custom', true).value;
      assert.ok(result >= min - 1e-6 && result <= max + 1e-6, 'custom convex bounds');
    }
  });

  test('sum weights returns total', () => {
    const rng = createRng(99);
    for (let i = 0; i < 200; i++) {
      const length = randInt(rng, 8) + 1;
      const values = new Array(length).fill(0).map(() => randFloat(rng, -5, 5));
      const metas = new Array(length).fill(null);
      const weights = OWAFusion.generateOWAWeights(length, 'sum', null, false);
      const result = OWAFusion.fuseWithMeta(values, metas, weights, 'sum', false).value;
      const expected = values.reduce((a, b) => a + b, 0);
      assert.ok(approxEqual(result, expected, 1e-6), 'sum matches total');
    }
  });

  test('sum_unbounded matches sum', () => {
    const rng = createRng(101);
    for (let i = 0; i < 200; i++) {
      const length = randInt(rng, 8) + 1;
      const values = new Array(length).fill(0).map(() => randFloat(rng, -5, 5));
      const metas = new Array(length).fill(null);
      const sumWeights = OWAFusion.generateOWAWeights(length, 'sum', null, false);
      const unboundedWeights = OWAFusion.generateOWAWeights(length, 'sum_unbounded', null, false);
      const sumResult = OWAFusion.fuseWithMeta(values, metas, sumWeights, 'sum', false).value;
      const unboundedResult = OWAFusion.fuseWithMeta(values, metas, unboundedWeights, 'sum_unbounded', false).value;
      assert.ok(approxEqual(sumResult, unboundedResult, 1e-6), 'sum_unbounded matches sum');
    }
  });

  test('priority weights are normalized proportions', () => {
    const priorities = [1, 5, 3];
    const weights = OWAFusion.generateOWAWeights(priorities.length, 'priority', priorities, true);
    const total = weights.reduce((sum, w) => sum + w, 0);
    assert.ok(approxEqual(total, 1.0, 1e-6));
    assert.ok(weights[1] > weights[2] && weights[2] > weights[0], 'weights follow priorities');
  });
});
