'use strict';

const test = require('node:test');
const assert = require('node:assert');
const SafetyController = require('../../src/safety/safetyController');

// --------------------------- fakes ---------------------------

function fakeMeasurements(values) {
  // values keyed by `${type}.${variant}.${position}` → number|null
  return {
    getUnit: (_type) => 'm3',
    type(t) {
      return {
        variant(v) {
          return {
            position(p) {
              return {
                getCurrentValue() {
                  const k = `${t}.${v}.${p}`;
                  return values[k];
                },
              };
            },
          };
        },
      };
    },
  };
}

function makeMachine(positionVsParent, operational = true) {
  const calls = [];
  return {
    config: { functionality: { positionVsParent } },
    _isOperationalState: () => operational,
    handleInput: (...args) => calls.push(args),
    calls,
  };
}

function makeStation() {
  const calls = [];
  return {
    handleInput: (...args) => calls.push(args),
    calls,
  };
}

function makeGroup() {
  const calls = [];
  return {
    turnOffAllMachines: () => calls.push(['turnOffAllMachines']),
    calls,
  };
}

function makeLogger() {
  const warns = [];
  return {
    warn: (msg) => warns.push(msg),
    info: () => {},
    error: () => {},
    debug: () => {},
    warns,
  };
}

function makeCtx({
  vol = 50,
  basin = { minVol: 10, maxVolAtOverflow: 90 },
  safety = {
    enableDryRunProtection: true,
    enableOverfillProtection: true,
    dryRunThresholdPercent: 10,
    overfillThresholdPercent: 95,
    timeleftToFullOrEmptyThresholdSeconds: 0,
  },
  machines = {},
  stations = {},
  machineGroups = {},
} = {}) {
  const measurements = fakeMeasurements({
    'volume.measured.atequipment': vol,
    'volume.predicted.atequipment': vol,
  });
  const logger = makeLogger();
  return {
    ctx: { measurements, basin, config: { safety }, logger, machines, stations, machineGroups },
    logger,
  };
}

// --------------------------- tests ---------------------------

test('normal volume + filling → not blocked, no shutdowns', () => {
  const m = makeMachine('downstream');
  const { ctx } = makeCtx({ vol: 50, machines: { m } });
  const sc = new SafetyController(ctx);
  const r = sc.evaluate({ direction: 'filling', secondsRemaining: 1000 });
  assert.deepStrictEqual(r, { blocked: false, reason: null, triggered: [] });
  assert.strictEqual(m.calls.length, 0);
});

test('dry-run trigger: low volume + draining → blocked, downstream shut down', () => {
  const down = makeMachine('downstream');
  const at = makeMachine('atequipment');
  const up = makeMachine('upstream');
  const station = makeStation();
  const group = makeGroup();
  const { ctx } = makeCtx({
    vol: 5, // below 10 * (1 + 10/100) = 11
    machines: { down, at, up },
    stations: { station },
    machineGroups: { group },
  });
  const sc = new SafetyController(ctx);
  const r = sc.evaluate({ direction: 'draining', secondsRemaining: 1000 });
  assert.strictEqual(r.blocked, true);
  assert.strictEqual(r.reason, 'dry-run');
  assert.ok(r.triggered.includes('dry-run-volume'));
  assert.deepStrictEqual(down.calls[0], ['parent', 'execSequence', 'shutdown']);
  assert.deepStrictEqual(at.calls[0], ['parent', 'execSequence', 'shutdown']);
  assert.strictEqual(up.calls.length, 0, 'upstream untouched in dry-run');
  assert.deepStrictEqual(station.calls[0], ['parent', 'execSequence', 'shutdown']);
  assert.deepStrictEqual(group.calls[0], ['turnOffAllMachines']);
});

test('dry-run does NOT trigger when filling', () => {
  const down = makeMachine('downstream');
  const { ctx } = makeCtx({ vol: 5, machines: { down } });
  const sc = new SafetyController(ctx);
  const r = sc.evaluate({ direction: 'filling', secondsRemaining: 1000 });
  // Filling at vol=5 (below overfill threshold 85.5) → no trigger at all.
  assert.strictEqual(r.blocked, false);
  assert.strictEqual(r.reason, null);
  assert.strictEqual(down.calls.length, 0);
});

test('overfill trigger: high volume + filling → not blocked, only upstream + station shut down', () => {
  const down = makeMachine('downstream');
  const at = makeMachine('atequipment');
  const up = makeMachine('upstream');
  const station = makeStation();
  const group = makeGroup();
  const { ctx } = makeCtx({
    vol: 88, // above 90 * 0.95 = 85.5
    machines: { down, at, up },
    stations: { station },
    machineGroups: { group },
  });
  const sc = new SafetyController(ctx);
  const r = sc.evaluate({ direction: 'filling', secondsRemaining: 1000 });
  assert.strictEqual(r.blocked, false, 'overfill must NOT block control');
  assert.strictEqual(r.reason, 'overfill');
  assert.ok(r.triggered.includes('overfill-volume'));
  assert.deepStrictEqual(up.calls[0], ['parent', 'execSequence', 'shutdown']);
  assert.strictEqual(down.calls.length, 0, 'downstream must keep running');
  assert.strictEqual(at.calls.length, 0, 'atequipment must keep running');
  assert.deepStrictEqual(station.calls[0], ['parent', 'execSequence', 'shutdown']);
  assert.strictEqual(group.calls.length, 0, 'machine groups must keep draining');
});

test('no volume data → blocked, all machines shut down (panic)', () => {
  const a = makeMachine('downstream');
  const b = makeMachine('upstream');
  const c = makeMachine('atequipment');
  // override measurements to return null
  const measurements = {
    getUnit: () => 'm3',
    type: () => ({ variant: () => ({ position: () => ({ getCurrentValue: () => null }) }) }),
  };
  const ctx = {
    measurements,
    basin: { minVol: 10, maxVolAtOverflow: 90 },
    config: { safety: { enableDryRunProtection: true, enableOverfillProtection: true, dryRunThresholdPercent: 10, overfillThresholdPercent: 95 } },
    logger: makeLogger(),
    machines: { a, b, c },
    stations: {},
    machineGroups: {},
  };
  const sc = new SafetyController(ctx);
  const r = sc.evaluate({ direction: 'steady', secondsRemaining: null });
  assert.strictEqual(r.blocked, true);
  assert.strictEqual(r.reason, 'no-volume-data');
  assert.deepStrictEqual(a.calls[0], ['parent', 'execSequence', 'shutdown']);
  assert.deepStrictEqual(b.calls[0], ['parent', 'execSequence', 'shutdown']);
  assert.deepStrictEqual(c.calls[0], ['parent', 'execSequence', 'shutdown']);
});

test('time-based protection: short remainingTime while draining triggers dry-run shutdowns', () => {
  const down = makeMachine('downstream');
  const { ctx } = makeCtx({
    vol: 50, // well above dry-run vol threshold
    safety: {
      enableDryRunProtection: false, // volume rule disabled
      enableOverfillProtection: false,
      dryRunThresholdPercent: 10,
      overfillThresholdPercent: 95,
      timeleftToFullOrEmptyThresholdSeconds: 60,
    },
    machines: { down },
  });
  const sc = new SafetyController(ctx);
  const r = sc.evaluate({ direction: 'draining', secondsRemaining: 30 });
  assert.strictEqual(r.blocked, true);
  assert.strictEqual(r.reason, 'dry-run');
  assert.ok(r.triggered.includes('time-remaining'));
  assert.deepStrictEqual(down.calls[0], ['parent', 'execSequence', 'shutdown']);
});

test('disabled rules: enableDryRunProtection=false + draining low → no trigger', () => {
  const down = makeMachine('downstream');
  const { ctx } = makeCtx({
    vol: 5, // would normally trigger dry-run
    safety: {
      enableDryRunProtection: false,
      enableOverfillProtection: false,
      dryRunThresholdPercent: 10,
      overfillThresholdPercent: 95,
      timeleftToFullOrEmptyThresholdSeconds: 0,
    },
    machines: { down },
  });
  const sc = new SafetyController(ctx);
  const r = sc.evaluate({ direction: 'draining', secondsRemaining: 1000 });
  assert.strictEqual(r.blocked, false);
  assert.strictEqual(r.reason, null);
  assert.strictEqual(down.calls.length, 0);
});
