import assert from "node:assert/strict";
import { describe, it } from "vitest";

// Pure unit tests for headless-uok-status formatting helpers.
// Extracted inline since importing from src/headless-uok-status.ts
// requires a full TS transform — these test the pure logic directly.

function passRate(entry) {
	if (entry.total === 0) return "—";
	return `${Math.round((entry.pass / entry.total) * 100)}%`;
}

function cbIcon(state) {
	if (state === "open") return "🔴";
	if (state === "half-open") return "🟡";
	return "🟢";
}

function coverageIcon(status) {
	switch (status) {
		case "ok":
			return "✓ ok";
		case "stale":
			return "⚠ stale";
		case "incomplete":
			return "⚠ incomplete";
		case "missing":
			return "⚠ missing";
		case "legacy":
			return "· legacy";
		default:
			return "";
	}
}

function formatTable(gates) {
	if (gates.length === 0) {
		return "No gate run data found in the last 24h.\n";
	}
	const header =
		"| Gate | Scope | Coverage | Pass% | Pass | Fail | Manual | Retry | CB | Streak | Last Evaluated |\n" +
		"|------|-------|----------|-------|------|------|--------|-------|----|--------|----------------|\n";
	const rows = gates
		.map((g) => {
			const last = g.lastEvaluatedAt
				? new Date(g.lastEvaluatedAt)
						.toISOString()
						.replace("T", " ")
						.slice(0, 19)
				: "never";
			return `| ${g.id} | ${g.scope} | ${coverageIcon(g.coverageStatus)} | ${passRate(g)} | ${g.pass} | ${g.fail} | ${g.manualAttention ?? 0} | ${g.retry} | ${cbIcon(g.circuitBreaker)} ${g.circuitBreaker} | ${g.failureStreak} | ${last} |`;
		})
		.join("\n");
	return `${header}${rows}\n`;
}

// Mirror of headless-uok-status.ts:classifyCoverage. Kept inline so the
// test file stays a pure unit test against the same logic shape (the
// .ts source isn't directly importable from this .mjs in the SF
// extension test tree). Slice 2 changed the second arg from a bool
// to the full meta row so we can distinguish "incomplete" from "ok".
const STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
function hasSchemaV2Metadata(meta) {
	return typeof meta?.surface === "string" && meta.surface.length > 0;
}
function isSchemaV2Complete(meta) {
	return (
		typeof meta?.surface === "string" &&
		meta.surface.length > 0 &&
		typeof meta?.runControl === "string" &&
		meta.runControl.length > 0 &&
		typeof meta?.permissionProfile === "string" &&
		meta.permissionProfile.length > 0 &&
		typeof meta?.traceId === "string" &&
		meta.traceId.length > 0
	);
}
function classifyCoverage(entry, meta, now = Date.now()) {
	if (!hasSchemaV2Metadata(meta)) return "legacy";
	if (!isSchemaV2Complete(meta)) return "incomplete";
	if (entry.total === 0) return "stale";
	const last = entry.lastEvaluatedAt ? Date.parse(entry.lastEvaluatedAt) : null;
	if (last !== null && now - last > STALE_THRESHOLD_MS) return "stale";
	return "ok";
}
const LEGACY_META = {
	surface: null,
	runControl: null,
	permissionProfile: null,
	traceId: null,
};
const COMPLETE_META = {
	surface: "headless",
	runControl: "supervised",
	permissionProfile: "high",
	traceId: "flow-123",
};

function makeGate(overrides = {}) {
	return {
		id: "verification",
		scope: "task",
		total: 10,
		pass: 8,
		fail: 2,
		manualAttention: 0,
		retry: 0,
		lastEvaluatedAt: "2026-05-11T12:00:00.000Z",
		circuitBreaker: "closed",
		failureStreak: 0,
		coverageStatus: "legacy",
		...overrides,
	};
}

describe("passRate", () => {
	it("returns_em_dash_when_total_is_zero", () => {
		assert.equal(passRate(makeGate({ total: 0 })), "—");
	});

	it("returns_100_when_all_pass", () => {
		assert.equal(passRate(makeGate({ total: 5, pass: 5 })), "100%");
	});

	it("returns_80_when_8_of_10_pass", () => {
		assert.equal(passRate(makeGate({ total: 10, pass: 8 })), "80%");
	});

	it("rounds_to_nearest_integer", () => {
		assert.equal(passRate(makeGate({ total: 3, pass: 1 })), "33%");
	});
});

describe("cbIcon", () => {
	it("returns_red_for_open", () => {
		assert.equal(cbIcon("open"), "🔴");
	});

	it("returns_yellow_for_half_open", () => {
		assert.equal(cbIcon("half-open"), "🟡");
	});

	it("returns_green_for_closed", () => {
		assert.equal(cbIcon("closed"), "🟢");
	});

	it("returns_green_for_unknown_state", () => {
		assert.equal(cbIcon("unknown"), "🟢");
	});
});

describe("formatTable", () => {
	it("returns_no_data_message_when_empty", () => {
		const result = formatTable([]);
		assert.ok(result.includes("No gate run data found"));
	});

	it("includes_header_row", () => {
		const result = formatTable([makeGate()]);
		assert.ok(result.includes("| Gate | Scope | Coverage | Pass%"));
	});

	it("includes_coverage_column_per_row", () => {
		const result = formatTable([
			makeGate({ id: "g1", coverageStatus: "legacy" }),
			makeGate({ id: "g2", coverageStatus: "ok" }),
			makeGate({ id: "g3", coverageStatus: "stale" }),
		]);
		assert.ok(result.includes("· legacy"));
		assert.ok(result.includes("✓ ok"));
		assert.ok(result.includes("⚠ stale"));
	});

	it("includes_manual_attention_column_distinct_from_fail", () => {
		// Codex audit follow-up: manualAttention used to be counted by
		// getGateRunStats but dropped from the rendered output, inflating
		// `total` invisibly. Now it's a column between Fail and Retry.
		const result = formatTable([
			makeGate({ id: "g-manual", fail: 0, manualAttention: 3 }),
		]);
		assert.ok(result.includes("| Manual |"), "header has Manual column");
		// The row contains "| 0 | 3 |" — fail=0, manualAttention=3.
		assert.ok(
			/\|\s*0\s*\|\s*3\s*\|/.test(result),
			"row shows manualAttention=3 distinct from fail=0",
		);
	});

	it("includes_separator_row", () => {
		const result = formatTable([makeGate()]);
		assert.ok(result.includes("|------|"));
	});

	it("includes_gate_id_in_row", () => {
		const result = formatTable([makeGate({ id: "cost-guard" })]);
		assert.ok(result.includes("cost-guard"));
	});

	it("includes_scope_in_row", () => {
		const result = formatTable([makeGate({ id: "Q5", scope: "task" })]);
		assert.ok(result.includes("task"));
	});

	it("includes_last_evaluated_header", () => {
		const result = formatTable([makeGate()]);
		assert.ok(result.includes("Last Evaluated"));
	});

	it("shows_never_when_lastEvaluatedAt_is_null", () => {
		const result = formatTable([makeGate({ lastEvaluatedAt: null })]);
		assert.ok(result.includes("never"));
	});

	it("shows_circuit_breaker_open_icon", () => {
		const result = formatTable([makeGate({ circuitBreaker: "open" })]);
		assert.ok(result.includes("🔴"));
		assert.ok(result.includes("open"));
	});

	it("shows_circuit_breaker_half_open_icon", () => {
		const result = formatTable([makeGate({ circuitBreaker: "half-open" })]);
		assert.ok(result.includes("🟡"));
	});

	it("shows_pass_rate_percentage", () => {
		const result = formatTable([makeGate({ total: 10, pass: 8 })]);
		assert.ok(result.includes("80%"));
	});

	it("formats_multiple_gates", () => {
		const gates = [
			makeGate({ id: "verification" }),
			makeGate({ id: "cost-guard", scope: "slice" }),
		];
		const result = formatTable(gates);
		assert.ok(result.includes("verification"));
		assert.ok(result.includes("cost-guard"));
	});

	it("formats_failure_streak", () => {
		const result = formatTable([makeGate({ failureStreak: 3 })]);
		assert.ok(result.includes("| 3 |"));
	});

	it("formats_timestamp_as_utc_date_string", () => {
		const result = formatTable([
			makeGate({ lastEvaluatedAt: "2026-05-11T12:00:00.000Z" }),
		]);
		assert.ok(result.includes("2026-05-11 12:00:00"));
	});
});

describe("classifyCoverage (slice 1+2: legacy / ok / stale / incomplete)", () => {
	// "now" pinned so stale-threshold math is reproducible across runs.
	const now = Date.parse("2026-05-14T12:00:00.000Z");

	it("returns_legacy_when_surface_is_null", () => {
		// All rows in the current quality_gates table predate schema v2.
		// Without surface set there is no schema-v2 metadata to detect,
		// so every existing row must classify as legacy.
		const status = classifyCoverage(
			makeGate({ total: 10, lastEvaluatedAt: "2026-05-14T11:00:00.000Z" }),
			LEGACY_META,
			now,
		);
		assert.equal(status, "legacy");
	});

	it("returns_legacy_even_when_rows_are_recent_and_surface_missing", () => {
		// "Legacy" wins over freshness — we never page operators about
		// rows that predate the schema-v2 writer.
		const status = classifyCoverage(
			makeGate({ total: 5, lastEvaluatedAt: "2026-05-14T11:59:00.000Z" }),
			LEGACY_META,
			now,
		);
		assert.equal(status, "legacy");
	});

	it("returns_ok_when_metadata_is_complete_and_runs_are_recent", () => {
		const status = classifyCoverage(
			makeGate({ total: 10, lastEvaluatedAt: "2026-05-14T11:00:00.000Z" }),
			COMPLETE_META,
			now,
		);
		assert.equal(status, "ok");
	});

	it("returns_incomplete_when_surface_is_set_but_other_required_fields_missing", () => {
		// Writer started populating surface but forgot runControl — the
		// classifier surfaces this so the operator can find the buggy
		// writer instead of seeing a silently "ok" row.
		const partial = { ...COMPLETE_META, runControl: null };
		const status = classifyCoverage(
			makeGate({ total: 10, lastEvaluatedAt: "2026-05-14T11:00:00.000Z" }),
			partial,
			now,
		);
		assert.equal(status, "incomplete");
	});

	it("returns_stale_when_metadata_complete_but_no_runs_in_window", () => {
		const status = classifyCoverage(
			makeGate({ total: 0, lastEvaluatedAt: "2026-05-12T00:00:00.000Z" }),
			COMPLETE_META,
			now,
		);
		assert.equal(status, "stale");
	});

	it("returns_stale_when_last_run_is_older_than_24h", () => {
		// Has runs but the most recent one is > 24h old.
		const status = classifyCoverage(
			makeGate({ total: 3, lastEvaluatedAt: "2026-05-12T11:59:00.000Z" }),
			COMPLETE_META,
			now,
		);
		assert.equal(status, "stale");
	});

	it("does_not_false_positive_on_empty_db", () => {
		// When the DB has no rows at all, no gate is even discovered,
		// so the classifier is never called. The status command short-
		// circuits with the "No gate run data found" message via
		// formatTable. This test asserts the contract from a different
		// angle: an empty gate list produces no warnings.
		const result = formatTable([]);
		assert.ok(result.includes("No gate run data found"));
		assert.ok(!result.includes("⚠"));
	});
});
