// Unit tests for shared library constants and helpers.
// Sanity-checks that v1.2 thresholds and domain-stakes table are exported
// with the expected shape. Detector-level behaviour is covered in
// per-detector test files (user-info, validation-seeking, stakes-matrix).

import { test, describe, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync, writeFileSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';

// Allocate a fresh data dir before importing lib.mjs, so SESSIONS_LOG points
// at a sandbox path. The lib.mjs module captures CLAUDE_PLUGIN_DATA at import
// time, so the env var must be set first.
const TEST_DATA_DIR = mkdtempSync(join(tmpdir(), 'ia-lib-test-'));
process.env.CLAUDE_PLUGIN_DATA = TEST_DATA_DIR;

const {
  TIER1_TURN_THRESHOLD,
  TIER2_SESSION_THRESHOLD,
  THRESHOLD_VALSEEK_FLAGS,
  DOMAIN_STAKES,
  HIGH_SYCOPHANCY_DOMAINS,
  HIGH_STAKES_DOMAINS,
  INFO_DOMAINS,
  SESSIONS_LOG,
  readRecentEndRecords,
} = await import('../hooks/scripts/lib.mjs');

after(() => {
  rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});

describe('v1.2 thresholds', () => {
  test('tier-1 turn threshold is 15', () => {
    assert.equal(TIER1_TURN_THRESHOLD, 15);
  });

  test('tier-2 session threshold is 3', () => {
    assert.equal(TIER2_SESSION_THRESHOLD, 3);
  });

  test('valseek high-stakes flag threshold is 3', () => {
    assert.equal(THRESHOLD_VALSEEK_FLAGS, 3);
  });
});

describe('DOMAIN_STAKES table', () => {
  test('default weight is 1.0', () => {
    assert.equal(DOMAIN_STAKES.default, 1.0);
  });

  test('high-stakes domains weighted 1.5', () => {
    assert.equal(DOMAIN_STAKES.legal, 1.5);
    assert.equal(DOMAIN_STAKES.parenting, 1.5);
    assert.equal(DOMAIN_STAKES.health, 1.5);
    assert.equal(DOMAIN_STAKES.financial, 1.5);
  });

  test('high-sycophancy domains weighted between 1.2 and 1.3', () => {
    assert.equal(DOMAIN_STAKES.relationship, 1.3);
    assert.equal(DOMAIN_STAKES.spirituality, 1.2);
  });

  test('table is frozen (immutable)', () => {
    assert.equal(Object.isFrozen(DOMAIN_STAKES), true);
  });

  test('uses singular domain identifiers (relationship, not relationships)', () => {
    assert.equal(DOMAIN_STAKES.relationship, 1.3);
    assert.equal(DOMAIN_STAKES.relationships, undefined);
  });
});

describe('domain classification arrays', () => {
  test('HIGH_SYCOPHANCY_DOMAINS contains relationship and spirituality', () => {
    assert.deepEqual([...HIGH_SYCOPHANCY_DOMAINS], ['relationship', 'spirituality']);
    assert.equal(Object.isFrozen(HIGH_SYCOPHANCY_DOMAINS), true);
  });

  test('HIGH_STAKES_DOMAINS contains legal, parenting, health, financial', () => {
    assert.deepEqual([...HIGH_STAKES_DOMAINS], ['legal', 'parenting', 'health', 'financial']);
    assert.equal(Object.isFrozen(HIGH_STAKES_DOMAINS), true);
  });

  test('INFO_DOMAINS adds professional to HIGH_STAKES_DOMAINS', () => {
    assert.deepEqual(
      [...INFO_DOMAINS],
      ['legal', 'parenting', 'health', 'financial', 'professional']
    );
    assert.equal(Object.isFrozen(INFO_DOMAINS), true);
  });
});

describe('readRecentEndRecords', () => {
  function writeFixture(records) {
    const lines = records.map(r => JSON.stringify(r)).join('\n') + '\n';
    writeFileSync(SESSIONS_LOG, lines);
  }

  test('returns N most recent end records in chronological order', () => {
    writeFixture([
      { session_id: 'a', start: '2026-05-01T10:00:00Z' }, // start record (no duration)
      { session_id: 'a', start: '2026-05-01T10:00:00Z', end: '2026-05-01T10:30:00Z', duration_min: 30 },
      { session_id: 'b', start: '2026-05-01T11:00:00Z' },
      { session_id: 'b', start: '2026-05-01T11:00:00Z', end: '2026-05-01T11:45:00Z', duration_min: 45 },
      { session_id: 'c', start: '2026-05-01T12:00:00Z', end: '2026-05-01T12:20:00Z', duration_min: 20 },
      { session_id: 'd', start: '2026-05-01T13:00:00Z', end: '2026-05-01T13:50:00Z', duration_min: 50 },
    ]);

    const recent = readRecentEndRecords(3);
    assert.equal(recent.length, 3);
    assert.equal(recent[0].session_id, 'b');
    assert.equal(recent[1].session_id, 'c');
    assert.equal(recent[2].session_id, 'd');
  });

  test('returns fewer than N when not enough end records exist', () => {
    writeFixture([
      { session_id: 'a', start: '2026-05-01T10:00:00Z', end: '2026-05-01T10:30:00Z', duration_min: 30 },
    ]);
    const recent = readRecentEndRecords(5);
    assert.equal(recent.length, 1);
    assert.equal(recent[0].session_id, 'a');
  });

  test('skips malformed JSON lines', () => {
    const goodA = JSON.stringify({ session_id: 'a', duration_min: 1 });
    const goodB = JSON.stringify({ session_id: 'b', duration_min: 2 });
    writeFileSync(SESSIONS_LOG, `${goodA}\nnot json\n${goodB}\n`);
    const recent = readRecentEndRecords(5);
    assert.equal(recent.length, 2);
    assert.equal(recent[0].session_id, 'a');
    assert.equal(recent[1].session_id, 'b');
  });

  test('empty file returns []', () => {
    writeFileSync(SESSIONS_LOG, '');
    assert.deepEqual(readRecentEndRecords(3), []);
  });

  test('missing file returns []', () => {
    rmSync(SESSIONS_LOG, { force: true });
    assert.deepEqual(readRecentEndRecords(3), []);
  });

  test('non-positive N returns []', () => {
    writeFixture([{ session_id: 'a', duration_min: 1 }]);
    assert.deepEqual(readRecentEndRecords(0), []);
    assert.deepEqual(readRecentEndRecords(-1), []);
  });
});
