// Basic tests for the calibration helpers.

const test = require('node:test');
const assert = require('node:assert/strict');

const { MeasurementContainer } = require('generalFunctions');
const {
  calibratePredictedVolume,
  calibratePredictedLevel,
  setManualInflow,
} = require('../../src/measurement/calibration');

function makeBasin() {
  return {
    surfaceArea: 10,
    minVol: 2,
    maxVol: 50,
    maxVolAtOverflow: 45,
    overflowLevel: 4.5,
    outflowLevel: 0.2,
    inflowLevel: 3,
  };
}

function makeCtx(seedVolume = null) {
  const measurements = new MeasurementContainer({
    autoConvert: true,
    preferredUnits: { flow: 'm3/s', level: 'm', volume: 'm3' },
  });
  const basin = makeBasin();
  if (seedVolume != null) {
    measurements.type('volume').variant('predicted').position('atequipment')
      .value(seedVolume, Date.now() - 5_000, 'm3').unit('m3');
  }
  const ctx = { measurements, basin };
  return ctx;
}

test('calibratePredictedVolume clears prior series and writes new value', async () => {
  const ctx = makeCtx(12);
  const before = ctx.measurements.type('volume').variant('predicted').position('atequipment')
    .getCurrentValue('m3');
  assert.ok(Math.abs(before - 12) < 1e-9);

  const ts = Date.now();
  calibratePredictedVolume(ctx, 30, ts);

  const m = ctx.measurements.type('volume').variant('predicted').position('atequipment').get();
  assert.equal(m.values.length, 1, 'series should hold exactly the calibration point');
  assert.ok(Math.abs(m.getCurrentValue() - 30) < 1e-9);

  // Level was derived: 30 / 10 = 3 m.
  const lvl = ctx.measurements.type('level').variant('predicted').position('atequipment')
    .getCurrentValue('m');
  assert.ok(Math.abs(lvl - 3) < 1e-9, `derived level was ${lvl}`);

  assert.equal(ctx._predictedFlowState.lastTimestamp, ts);
  assert.equal(ctx._predictedFlowState.inflow, 0);
  assert.equal(ctx._predictedFlowState.outflow, 0);
});

test('calibratePredictedLevel writes both level and derived volume', async () => {
  const ctx = makeCtx(2);
  calibratePredictedLevel(ctx, 4.0, Date.now(), 'm');

  const lvl = ctx.measurements.type('level').variant('predicted').position('atequipment')
    .getCurrentValue('m');
  assert.ok(Math.abs(lvl - 4.0) < 1e-9);

  const vol = ctx.measurements.type('volume').variant('predicted').position('atequipment')
    .getCurrentValue('m3');
  assert.ok(Math.abs(vol - 40) < 1e-9, `derived volume was ${vol}`);
});

test('setManualInflow writes to flow.predicted.in.manual-qin', async () => {
  const ctx = makeCtx();
  const ts = Date.now();
  setManualInflow(ctx, 0.025, ts, 'm3/s');

  const series = ctx.measurements.type('flow').variant('predicted').position('in').child('manual-qin');
  const val = series.getCurrentValue('m3/s');
  assert.ok(Math.abs(val - 0.025) < 1e-9, `manual-qin value was ${val}`);

  // It must NOT collide with the default child bucket.
  const defaultBucket = ctx.measurements.measurements?.flow?.predicted?.in?.default;
  assert.equal(defaultBucket, undefined);
});

test('calibration uses ctx.flowAggregator.resetState when present', async () => {
  const ctx = makeCtx(5);
  let resetCalled = null;
  ctx.flowAggregator = { resetState: (ts) => { resetCalled = ts; } };

  const ts = 1234567890;
  calibratePredictedVolume(ctx, 20, ts);

  assert.equal(resetCalled, ts);
  // The plain bag should NOT be touched when the aggregator hook is present.
  assert.equal(ctx._predictedFlowState, undefined);
});

test('calibratePredictedVolume rejects bad context', async () => {
  assert.throws(() => calibratePredictedVolume({}, 10));
  assert.throws(() => calibratePredictedLevel({}, 1.0));
  assert.throws(() => setManualInflow({}, 0.01));
});
