#!/usr/bin/env node
/**
 * Unit tests for overlay-merger marker stability + version-pin refresh (v4.11.0).
 *
 * Run: node src/utils/__tests__/overlay-merger.test.js
 * Exits 0 on success, non-zero on failure (CI-friendly via node:test).
 *
 * Contract under test:
 *   - The generated-file marker is CONTENT-ADDRESSED only (base_sha + overlay_sha)
 *     so `mergeOverlay` produces BYTE-IDENTICAL output across version-only bumps —
 *     killing the per-update reconcile churn (Trigger A).
 *   - `readMarker` still reads legacy markers that carry `base_version=`.
 *   - `refreshOverlayVersionPin` only rewrites the pin when the base is unchanged
 *     (sha matches), never masking real drift.
 */
const test = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
const os = require('os');
const path = require('path');

const {
  mergeOverlay, readMarker, isGeneratedFile, computeBaseFileSha,
  refreshOverlayVersionPin, MARKER_PREFIX,
} = require('../overlay-merger');

const BASE = `---
name: coder
description: coding agent
---

Preamble line.

## Role
You write code.

## Rules
Be careful.
`;

const OVERLAY = `---
base_agent: coder
base_agent_version: 4.9.0
base_file_sha: "${computeBaseFileSha(BASE)}"
mode: extend
---

## [APPEND] Rules
Always run the linter.
`;

test('marker is byte-stable across version-only bumps (Trigger A fix)', () => {
  const a = mergeOverlay({ kind: 'agent', name: 'coder', baseContent: BASE, overlayContent: OVERLAY, frameworkVersion: '4.10.0' });
  const b = mergeOverlay({ kind: 'agent', name: 'coder', baseContent: BASE, overlayContent: OVERLAY, frameworkVersion: '4.11.0' });
  assert.strictEqual(a.generated, b.generated, 'generated output must be identical when only frameworkVersion changes');
});

test('generated marker no longer stamps base_version', () => {
  const { generated } = mergeOverlay({ kind: 'agent', name: 'coder', baseContent: BASE, overlayContent: OVERLAY, frameworkVersion: '4.11.0' });
  assert.ok(generated.includes(MARKER_PREFIX), 'must carry the generated marker');
  assert.ok(!/base_version=/.test(generated), 'marker must NOT contain base_version=');
  assert.ok(/base_sha=/.test(generated), 'marker must keep base_sha=');
  assert.ok(/overlay_sha=/.test(generated), 'marker must keep overlay_sha=');
});

test('readMarker reads NEW marker (no base_version)', () => {
  const { generated } = mergeOverlay({ kind: 'agent', name: 'coder', baseContent: BASE, overlayContent: OVERLAY, frameworkVersion: '4.11.0' });
  const m = readMarker(generated);
  assert.ok(m, 'marker must be detected');
  assert.strictEqual(m.kind, 'agent');
  assert.strictEqual(m.name, 'coder');
  assert.strictEqual(m.baseVersion, null, 'new markers have no base_version');
  assert.ok(m.baseSha && m.overlaySha);
  assert.ok(isGeneratedFile(generated));
});

test('readMarker still reads LEGACY marker (with base_version)', () => {
  const legacy = `---\nname: coder\n---\n${MARKER_PREFIX} kind=agent name=coder base_version=4.5.0 base_sha=abc123def456 overlay_sha=deadbeef0000\n   DO NOT EDIT. -->\n\n## Role\nx\n`;
  const m = readMarker(legacy);
  assert.ok(m, 'legacy marker must still parse');
  assert.strictEqual(m.baseVersion, '4.5.0');
  assert.strictEqual(m.baseSha, 'abc123def456');
  assert.strictEqual(m.overlaySha, 'deadbeef0000');
  assert.ok(isGeneratedFile(legacy));
});

function withTempOverlay(content, fn) {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'baldart-pin-'));
  const p = path.join(dir, 'coder.md');
  fs.writeFileSync(p, content);
  try { return fn(p); } finally { fs.rmSync(dir, { recursive: true, force: true }); }
}

test('refreshOverlayVersionPin: refreshes when sha matches and version differs', () => {
  withTempOverlay(OVERLAY, (p) => {
    const r = refreshOverlayVersionPin(p, 'agent', '4.11.0', computeBaseFileSha(BASE));
    assert.strictEqual(r.changed, true, r.reason);
    assert.match(fs.readFileSync(p, 'utf8'), /base_agent_version:\s*4\.11\.0/);
  });
});

test('refreshOverlayVersionPin: NO-OP when base sha mismatches (drift preserved)', () => {
  withTempOverlay(OVERLAY, (p) => {
    const r = refreshOverlayVersionPin(p, 'agent', '4.11.0', 'feedfacefeed'); // wrong sha → base changed
    assert.strictEqual(r.changed, false);
    assert.match(fs.readFileSync(p, 'utf8'), /base_agent_version:\s*4\.9\.0/, 'stale pin must remain as a drift signal');
  });
});

test('refreshOverlayVersionPin: NO-OP when already current', () => {
  const overlayCurrent = OVERLAY.replace('4.9.0', '4.11.0');
  withTempOverlay(overlayCurrent, (p) => {
    const r = refreshOverlayVersionPin(p, 'agent', '4.11.0', computeBaseFileSha(BASE));
    assert.strictEqual(r.changed, false);
    assert.strictEqual(r.reason, 'already-current');
  });
});
