import { test, describe, it, beforeEach, before } from 'node:test';
import assert from 'node:assert/strict';
import { ValueManager } from '../../src/core/ValueManager.js';
import { UnifiedKeyManager } from '../../src/core/UnifiedKeyManager.js';
import { ValueContext } from '../../src/authorization/ValueContext.js';

// Minimal mock RelationManager
class MockRelationManager {
  constructor(relations) {
    this._relations = relations;
  }
  getAllValueRelationsFromSrc(entityId, relationName) {
    return this._relations.filter(r => r.src === entityId && r.rel === relationName);
  }
  getRawValueRelationsByName(relationName) {
    return this._relations.filter(r => r.rel === relationName);
  }
  getRawValueRelationsForLocalContext() {
    return []; // Not used in basic tests
  }
}

// Minimal mock Arbiter
class MockArbiter {
  constructor(relations) {
    this.relationManager = new MockRelationManager(relations);
    this.nodeIdByKey = new Map();
    this.keyByNodeId = new Map();
    this.keyManager = new UnifiedKeyManager();
    relations.forEach(r => {
      this.nodeIdByKey.set(r.srcKey, r.src);
      this.nodeIdByKey.set(r.dstKey, r.dst);
      this.keyByNodeId.set(r.src, r.srcKey);
      this.keyByNodeId.set(r.dst, r.dstKey);
    });
  }

  resolveKey(id) {
    return this.keyByNodeId.get(id);
  }

  resolveNodeId(key) {
    return this.nodeIdByKey.get(key);
  }
}

describe('ValueManager & ValueContext', () => {
  let relations, arbiter, valueManager, valueContext;
  const now = Date.now();

  beforeEach(() => {
    relations = [
      { src: 1, dst: 2, rel: 'balance', value: 100, possibility: 1, reliability: 1, changed_last_at: now, srcKey: 'user:alice', dstKey: 'account:checking' },
      { src: 1, dst: 3, rel: 'balance', value: 200, possibility: 0.8, reliability: 1, changed_last_at: now - 3600 * 1000, srcKey: 'user:alice', dstKey: 'account:savings' },
      { src: 2, dst: 4, rel: 'price', value: 50, possibility: 1, reliability: 1, changed_last_at: now, srcKey: 'feature:basic', dstKey: 'price:basic' },
    ];
    arbiter = new MockArbiter(relations);
    valueManager = new ValueManager(arbiter);
    valueContext = new ValueContext(arbiter);
  });

  it('extracts and blurs values correctly', () => {
    const rel = relations[0];
    const blurred = valueManager.getBlurredValue(rel);
    assert.ok(blurred.interval);
    const EPS = 1e-6;
    const expectedMin = rel.value;
    const expectedMax = rel.value;
    assert.ok(Math.abs(blurred.interval.min - expectedMin) < EPS, `min: ${blurred.interval.min} vs ${expectedMin}`);
    assert.ok(Math.abs(blurred.interval.max - expectedMax) < EPS, `max: ${blurred.interval.max} vs ${expectedMax}`);
    assert.ok(Math.abs(blurred.possibility - 1) < EPS);
  });

  it('applies decay and blur for old values', () => {
    const rel = relations[1];
    // Use default config: decay should apply
    const blurred = valueManager.getBlurredValue(rel);
    assert.ok(blurred.interval);
    assert.ok(blurred.possibility === rel.possibility);
    assert.ok(blurred.interval.min === rel.value);
    assert.ok(blurred.interval.max === rel.value);
  });

  it('caches values in ValueContext', () => {
    const vals1 = valueContext.getValues(1, 'balance');
    const vals2 = valueContext.getValues(1, 'balance');
    assert.deepEqual(vals1, vals2);
    assert.ok(valueContext.cacheHits > 0);
  });

  it('aggregates values (max, min, sum, average)', () => {
    const max = valueContext.getAggregatedValue(1, 'balance', 'max');
    assert.equal(max.value, 200);
    const min = valueContext.getAggregatedValue(1, 'balance', 'min');
    assert.equal(min.value, 100);
    const sum = valueContext.getAggregatedValue(1, 'balance', 'sum');
    assert.equal(sum.value, 300);
    const avg = valueContext.getAggregatedValue(1, 'balance', 'average');
    assert.ok(Math.abs(avg.value - 150) < 1e-6);
  });

  it('returns empty for missing values', () => {
    const vals = valueContext.getValues(99, 'balance');
    assert.deepEqual(vals, []);
    const agg = valueContext.getAggregatedValue(99, 'balance', 'max');
    assert.equal(agg.hasValue, false);
    assert.equal(agg.value, null);
  });
});

describe('OWAFusion', () => {
  let OWAFusion;
  before(async () => {
    ({ OWAFusion } = await import('../../src/utils/OWAFusion.js'));
  });
  it('aggregates values with max, min, average, sum, custom', () => {
    const values = [10, 20, 30];
    const metas = [{ label: 'a' }, { label: 'b' }, { label: 'c' }];
    // Max
    const max = OWAFusion.fuseWithMeta(values, metas, null, 'max');
    assert.ok(Math.abs(max.value - 30) < 1e-6);
    // Min
    const min = OWAFusion.fuseWithMeta(values, metas, null, 'min');
    assert.ok(Math.abs(min.value - 10) < 1e-6);
    // Average
    const avg = OWAFusion.fuseWithMeta(values, metas, null, 'average');
    assert.ok(Math.abs(avg.value - 20) < 1e-6);
    // Sum
    const sum = OWAFusion.fuseWithMeta(values, metas, null, 'sum', false);
    assert.ok(Math.abs(sum.value - 60) < 1e-6);
    // Custom weights (0.2, 0.3, 0.5) - OWA sorts values descending
    const custom = OWAFusion.fuseWithMeta(values, metas, [0.2, 0.3, 0.5], 'custom');
    const sortedValues = [...values].sort((a, b) => b - a); // [30, 20, 10]
    const customWeights = [0.2, 0.3, 0.5];
    const expected = sortedValues.reduce((sum, v, i) => sum + v * customWeights[i], 0);
    assert.ok(Math.abs(custom.value - expected) < 1e-6);
  });

  it('aggregates intervals with max, min, sum, average', () => {
    const intervals = [
      { min: 1, max: 2 },
      { min: 3, max: 4 },
      { min: 5, max: 6 }
    ];
    const metas = [{ label: 'a' }, { label: 'b' }, { label: 'c' }];
    // Max
    const max = OWAFusion.fuseIntervalsWithMeta(intervals, metas, null, 'max');
    assert.ok(Math.abs(max.interval.max - 6) < 1e-6);
    // Min
    const min = OWAFusion.fuseIntervalsWithMeta(intervals, metas, null, 'min');
    assert.ok(Math.abs(min.interval.min - 1) < 1e-6);
    // Sum
    const sum = OWAFusion.fuseIntervalsWithMeta(intervals, metas, null, 'sum');
    assert.ok(Math.abs(sum.interval.min - 9) < 1e-6);
    assert.ok(Math.abs(sum.interval.max - 12) < 1e-6);
    // Average
    const avg = OWAFusion.fuseIntervalsWithMeta(intervals, metas, null, 'average');
    assert.ok(Math.abs(avg.interval.min - 3) < 1e-6);
    assert.ok(Math.abs(avg.interval.max - 4) < 1e-6);
    // Custom weights (0.5, 0.3, 0.2) - OWA sorts intervals by midpoint descending
    const custom = OWAFusion.fuseIntervalsWithMeta(intervals, metas, [0.5, 0.3, 0.2], 'custom');
    const sortedIntervals = [...intervals].sort((a, b) => ((b.min + b.max) / 2) - ((a.min + a.max) / 2));
    const customWeights = [0.5, 0.3, 0.2];
    const expectedMin = sortedIntervals.reduce((sum, iv, i) => sum + iv.min * customWeights[i], 0);
    const expectedMax = sortedIntervals.reduce((sum, iv, i) => sum + iv.max * customWeights[i], 0);
    assert.ok(Math.abs(custom.interval.min - expectedMin) < 1e-6);
    assert.ok(Math.abs(custom.interval.max - expectedMax) < 1e-6);
  });
}); 
