/**
 * uok-metrics-exposition.test.mjs — Prometheus metrics cache behaviour.
 *
 * Purpose: prove cached UOK metric snapshots are explicitly refreshable so
 * health surfaces can trade scrape cost for freshness without serving stale
 * data after known DB writes.
 */
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";
import { closeDatabase, insertUokMessage, openDatabase } from "../sf-db.js";
import {
	invalidateMetricsCache,
	readUokMetrics,
	writeUokMetrics,
} from "../uok/metrics-exposition.js";
import { appendTraceEvent } from "../uok/trace-writer.js";

const tmpDirs = [];
let _currentProject = null;

afterEach(() => {
	closeDatabase();
	invalidateMetricsCache();
	_currentProject = null;
	while (tmpDirs.length > 0) {
		const dir = tmpDirs.pop();
		if (dir) rmSync(dir, { recursive: true, force: true });
	}
});

function makeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-uok-metrics-"));
	mkdirSync(join(dir, ".sf"), { recursive: true });
	tmpDirs.push(dir);
	// Open DB at the real project path so currentPath derives correct basePath
	openDatabase(join(dir, ".sf", "sf.db"));
	_currentProject = dir;
	return dir;
}

function recordGateRun(
	basePath,
	outcome,
	evaluatedAt = new Date().toISOString(),
) {
	appendTraceEvent(basePath, `trace-${outcome}-${Date.now()}`, {
		type: "gate_run",
		traceId: `trace-${outcome}`,
		turnId: `turn-${outcome}`,
		gateId: "cache-gate",
		gateType: "verification",
		unitType: "execute-task",
		unitId: "M001/S01/T01",
		milestoneId: "M001",
		sliceId: "S01",
		taskId: "T01",
		outcome,
		failureClass: outcome === "pass" ? "" : "assertion",
		rationale: outcome,
		findings: "",
		attempt: 1,
		maxAttempts: 1,
		retryable: false,
		evaluatedAt,
		durationMs: outcome === "pass" ? 10 : 20,
	});
}

test("writeUokMetrics_when_cache_invalidated_refreshes_db_snapshot", () => {
	const project = makeProject();
	recordGateRun(project, "pass");

	writeUokMetrics(project, ["cache-gate"]);
	const first = readUokMetrics(project);
	assert.match(first, /uok_gate_runs_total\{gate_id="cache-gate"\} 1/);

	recordGateRun(project, "fail");
	writeUokMetrics(project, ["cache-gate"]);
	const cached = readUokMetrics(project);
	assert.match(cached, /uok_gate_runs_total\{gate_id="cache-gate"\} 1/);

	invalidateMetricsCache();
	writeUokMetrics(project, ["cache-gate"]);
	const refreshed = readUokMetrics(project);
	assert.match(refreshed, /uok_gate_runs_total\{gate_id="cache-gate"\} 2/);
	assert.match(
		refreshed,
		/uok_gate_runs_failed_total\{gate_id="cache-gate"\} 1/,
	);
});

test("writeUokMetrics_includes_message_bus_metrics", () => {
	const project = makeProject();
	insertUokMessage({
		id: "msg-1",
		from: "agent-a",
		to: "agent-b",
		body: "hello",
		sentAt: new Date().toISOString(),
		metadata: {},
	});
	writeUokMetrics(project);
	const text = readUokMetrics(project);
	assert.match(text, /uok_message_bus_total_messages 1/);
	assert.match(text, /uok_message_bus_unread_messages 1/);
	assert.match(text, /uok_message_bus_unique_agents 1/);
	assert.match(text, /uok_message_bus_unique_conversations 1/);
});
