// tests/kb-update/test-log-rotate.test.mjs
// Unit tests for scripts/kb-update/lib/log-rotate.mjs

import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
  mkdtempSync,
  rmSync,
  writeFileSync,
  readFileSync,
  existsSync,
  statSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { rotateLog } from '../../scripts/kb-update/lib/log-rotate.mjs';

function withTmp(fn) {
  const dir = mkdtempSync(join(tmpdir(), 'lr-test-'));
  try {
    return fn(dir);
  } finally {
    rmSync(dir, { recursive: true, force: true });
  }
}

test('rotateLog — missing log is a no-op', () => {
  withTmp((dir) => {
    const log = join(dir, 'kb.log');
    const result = rotateLog(log, { maxSizeBytes: 100 });
    assert.equal(result.rotated, false);
    assert.equal(existsSync(log), false);
  });
});

test('rotateLog — under-size log is not rotated', () => {
  withTmp((dir) => {
    const log = join(dir, 'kb.log');
    writeFileSync(log, 'tiny', 'utf8');
    const before = statSync(log).size;
    const result = rotateLog(log, { maxSizeBytes: 1024 });
    assert.equal(result.rotated, false);
    assert.equal(statSync(log).size, before);
    assert.equal(existsSync(`${log}.1`), false);
  });
});

test('rotateLog — over-size log moves to .1 with original content', () => {
  withTmp((dir) => {
    const log = join(dir, 'kb.log');
    const content = 'x'.repeat(2048);
    writeFileSync(log, content, 'utf8');
    const result = rotateLog(log, { maxSizeBytes: 1024 });
    assert.equal(result.rotated, true);
    assert.equal(existsSync(log), false);
    assert.equal(existsSync(`${log}.1`), true);
    assert.equal(readFileSync(`${log}.1`, 'utf8'), content);
  });
});

test('rotateLog — chained rotation across 6 calls keeps at most maxGenerations files', () => {
  withTmp((dir) => {
    const log = join(dir, 'kb.log');
    const max = 5; // 1 active + 4 rotated; lastGen = 4
    for (let i = 0; i < 6; i++) {
      writeFileSync(log, `gen-${i}-${'x'.repeat(2048)}`, 'utf8');
      rotateLog(log, { maxSizeBytes: 1024, maxGenerations: max });
    }
    // After 6 over-size rotations, keep .1..(max-1) = .1..4 from the latest
    // chain. Active log was just rotated → no active log on disk.
    assert.equal(existsSync(log), false);
    for (let i = 1; i <= max - 1; i++) {
      assert.equal(existsSync(`${log}.${i}`), true, `expected ${log}.${i}`);
    }
    assert.equal(existsSync(`${log}.${max}`), false);
    assert.equal(existsSync(`${log}.${max + 1}`), false);
  });
});

test('rotateLog — existing oldest generation is dropped on next rotation', () => {
  withTmp((dir) => {
    const log = join(dir, 'kb.log');
    // Pre-seed an oldest generation so we can prove it gets dropped.
    writeFileSync(`${log}.4`, 'oldest-content', 'utf8');
    writeFileSync(log, 'x'.repeat(2048), 'utf8');
    const result = rotateLog(log, { maxSizeBytes: 1024, maxGenerations: 5 });
    assert.equal(result.rotated, true);
    // .4 dropped; new .1 created from prior active; nothing at .4 since
    // there were no .3/.2 to shift up.
    assert.equal(result.dropped, `${log}.4`);
    assert.equal(existsSync(`${log}.4`), false);
    assert.equal(existsSync(`${log}.1`), true);
  });
});

test('rotateLog — maxGenerations=1 keeps only the active slot (no .1)', () => {
  withTmp((dir) => {
    const log = join(dir, 'kb.log');
    writeFileSync(log, 'x'.repeat(2048), 'utf8');
    const result = rotateLog(log, { maxSizeBytes: 1024, maxGenerations: 1 });
    assert.equal(result.rotated, true);
    assert.equal(existsSync(log), false);
    assert.equal(existsSync(`${log}.1`), false);
    assert.equal(result.dropped, log);
  });
});

test('rotateLog — preserves rotated content across two-step shift', () => {
  withTmp((dir) => {
    const log = join(dir, 'kb.log');
    // First rotation
    writeFileSync(log, `first-${'x'.repeat(2048)}`, 'utf8');
    rotateLog(log, { maxSizeBytes: 1024, maxGenerations: 3 });
    assert.equal(readFileSync(`${log}.1`, 'utf8').startsWith('first-'), true);

    // Second rotation: prior .1 should shift to .2; new .1 from second active.
    writeFileSync(log, `second-${'x'.repeat(2048)}`, 'utf8');
    rotateLog(log, { maxSizeBytes: 1024, maxGenerations: 3 });
    assert.equal(readFileSync(`${log}.1`, 'utf8').startsWith('second-'), true);
    assert.equal(readFileSync(`${log}.2`, 'utf8').startsWith('first-'), true);
  });
});

test('rotateLog — empty logPath rejected', () => {
  assert.throws(() => rotateLog(''), /logPath is required/);
  assert.throws(() => rotateLog(undefined), /logPath is required/);
});
