/**
 * uok-execution-graph-persist.test.mjs — Execution graph persistence contract tests.
 *
 * Purpose: verify SQLite schema creation, graph/node/progress CRUD, and
 * query surfaces for /tasks display.
 */

import assert from "node:assert/strict";
import { afterEach, test } from "vitest";
import { closeDatabase, getDatabase, openDatabase } from "../sf-db.js";
import {
	ensureExecutionGraphSchema,
	getGraphProgressStream,
	getGraphStateSummary,
	persistFullGraph,
	persistGraphNode,
	persistGraphSnapshot,
	persistProgressEvent,
	queryTasksByState,
} from "../uok/execution-graph-persist.js";

afterEach(() => {
	closeDatabase();
});

function makeDb() {
	assert.equal(openDatabase(":memory:"), true);
	return getDatabase();
}

// ─── Schema ────────────────────────────────────────────────────────────────

test("ensureExecutionGraphSchema_creates_tables_and_indexes", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	const tables = db
		.prepare("SELECT name FROM sqlite_master WHERE type='table'")
		.all()
		.map((r) => r.name);
	assert.ok(tables.includes("uok_execution_graphs"));
	assert.ok(tables.includes("uok_graph_nodes"));
	assert.ok(tables.includes("uok_graph_progress"));

	const indexes = db
		.prepare("SELECT name FROM sqlite_master WHERE type='index'")
		.all()
		.map((r) => r.name);
	assert.ok(indexes.some((n) => n.includes("idx_graph_nodes_graph")));
	assert.ok(indexes.some((n) => n.includes("idx_graph_nodes_status")));
});

// ─── Graph Snapshot ────────────────────────────────────────────────────────

test("persistGraphSnapshot_inserts_new_graph", () => {
	const db = makeDb();
	persistGraphSnapshot(db, {
		id: "g1",
		milestoneId: "M001",
		sessionId: "sess-1",
		phase: "executing",
		nodeCount: 5,
		createdAt: new Date().toISOString(),
		status: "active",
	});

	const row = db
		.prepare("SELECT * FROM uok_execution_graphs WHERE id = :id")
		.get({ id: "g1" });
	assert.equal(row.id, "g1");
	assert.equal(row.milestone_id, "M001");
	assert.equal(row.status, "active");
});

test("persistGraphSnapshot_updates_existing_graph", () => {
	const db = makeDb();
	const id = "g1";
	persistGraphSnapshot(db, {
		id,
		milestoneId: "M001",
		phase: "executing",
		nodeCount: 5,
		createdAt: new Date().toISOString(),
		status: "active",
	});

	persistGraphSnapshot(db, {
		id,
		milestoneId: "M001",
		phase: "completing-milestone",
		nodeCount: 5,
		createdAt: new Date().toISOString(),
		status: "completed",
	});

	const row = db
		.prepare("SELECT * FROM uok_execution_graphs WHERE id = :id")
		.get({ id });
	assert.equal(row.phase, "completing-milestone");
	assert.equal(row.status, "completed");
	assert.ok(row.completed_at);
});

// ─── Graph Node ────────────────────────────────────────────────────────────

test("persistGraphNode_inserts_node", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistGraphNode(db, "g1", {
		id: "n1",
		kind: "unit",
		unitType: "execute-task",
		unitId: "M001.S01.T01",
		milestoneId: "M001",
		sliceId: "S01",
		taskId: "T01",
		title: "Implement auth",
		dependsOn: ["n0"],
		writes: ["src/auth.js"],
		state: "todo",
	});

	const row = db
		.prepare("SELECT * FROM uok_graph_nodes WHERE id = :id")
		.get({ id: "n1" });
	assert.equal(row.id, "n1");
	assert.equal(row.unit_type, "execute-task");
	assert.equal(row.status, "todo");
	assert.equal(JSON.parse(row.depends_on)[0], "n0");
});

test("persistGraphNode_updates_existing_node", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistGraphNode(db, "g1", {
		id: "n1",
		kind: "unit",
		unitType: "execute-task",
		unitId: "M001.S01.T01",
		state: "todo",
	});

	persistGraphNode(db, "g1", {
		id: "n1",
		kind: "unit",
		unitType: "execute-task",
		unitId: "M001.S01.T01",
		state: "done",
		workerId: "w-1",
		durationMs: 1500,
		gateResults: [{ outcome: "pass", gateId: "security" }],
	});

	const row = db
		.prepare("SELECT * FROM uok_graph_nodes WHERE id = :id")
		.get({ id: "n1" });
	assert.equal(row.status, "done");
	assert.equal(row.worker_id, "w-1");
	assert.equal(row.duration_ms, 1500);
});

// ─── Full Graph Persistence ────────────────────────────────────────────────

test("persistFullGraph_writes_snapshot_and_nodes_in_transaction", () => {
	const db = makeDb();
	const nodes = [
		{
			id: "n1",
			kind: "unit",
			unitType: "execute-task",
			unitId: "U1",
			state: "todo",
		},
		{
			id: "n2",
			kind: "unit",
			unitType: "execute-task",
			unitId: "U2",
			state: "todo",
		},
	];

	persistFullGraph(db, "g2", nodes, {
		milestoneId: "M002",
		phase: "executing",
	});

	const graph = db
		.prepare("SELECT * FROM uok_execution_graphs WHERE id = :id")
		.get({ id: "g2" });
	assert.equal(graph.node_count, 2);

	const nodeRows = db
		.prepare("SELECT * FROM uok_graph_nodes WHERE graph_id = :id")
		.all({ id: "g2" });
	assert.equal(nodeRows.length, 2);
});

// ─── Query Tasks ───────────────────────────────────────────────────────────

test("queryTasksByState_filters_by_state", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistGraphNode(db, "g1", {
		id: "n1",
		kind: "unit",
		unitId: "U1",
		state: "todo",
	});
	persistGraphNode(db, "g1", {
		id: "n2",
		kind: "unit",
		unitId: "U2",
		state: "done",
	});
	persistGraphNode(db, "g1", {
		id: "n3",
		kind: "unit",
		unitId: "U3",
		state: "running",
	});

	const todo = queryTasksByState(db, { graphId: "g1", states: ["todo"] });
	assert.equal(todo.length, 1);
	assert.equal(todo[0].id, "n1");

	const done = queryTasksByState(db, { graphId: "g1", states: ["done"] });
	assert.equal(done.length, 1);
	assert.equal(done[0].id, "n2");

	const running = queryTasksByState(db, {
		graphId: "g1",
		states: ["in_progress"],
	});
	assert.equal(running.length, 1);
	assert.equal(running[0].id, "n3");
	assert.equal(running[0].status, "running");
});

test("queryTasksByState_filters_by_milestone", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistGraphNode(db, "g1", {
		id: "n1",
		kind: "unit",
		unitId: "U1",
		milestoneId: "M001",
		state: "todo",
	});
	persistGraphNode(db, "g1", {
		id: "n2",
		kind: "unit",
		unitId: "U2",
		milestoneId: "M002",
		state: "todo",
	});

	const m1 = queryTasksByState(db, { milestoneId: "M001" });
	assert.equal(m1.length, 1);
	assert.equal(m1[0].id, "n1");
});

test("queryTasksByState_parses_json_columns", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistGraphNode(db, "g1", {
		id: "n1",
		kind: "unit",
		unitId: "U1",
		state: "done",
		dependsOn: ["n0"],
		writes: ["a.js", "b.js"],
		gateResults: [{ outcome: "pass", gateId: "g1" }],
	});

	const tasks = queryTasksByState(db, { graphId: "g1" });
	assert.equal(tasks[0].dependsOn.length, 1);
	assert.equal(tasks[0].writes.length, 2);
	assert.equal(tasks[0].gateResults.length, 1);
});

// ─── State Summary ─────────────────────────────────────────────────────────

test("getGraphStateSummary_computes_counts_and_progress", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistGraphNode(db, "g1", {
		id: "n1",
		kind: "unit",
		unitId: "U1",
		state: "todo",
	});
	persistGraphNode(db, "g1", {
		id: "n2",
		kind: "unit",
		unitId: "U2",
		state: "in_progress",
	});
	persistGraphNode(db, "g1", {
		id: "n3",
		kind: "unit",
		unitId: "U3",
		state: "done",
	});
	persistGraphNode(db, "g1", {
		id: "n4",
		kind: "unit",
		unitId: "U4",
		state: "done",
	});

	const summary = getGraphStateSummary(db, "g1");
	assert.equal(summary.total, 4);
	assert.equal(summary.terminal, 2);
	assert.equal(summary.progress, 50);
	assert.equal(summary.isComplete, false);
	assert.equal(summary.counts.todo, 1);
	assert.equal(summary.counts.running, 1);
	assert.equal(summary.counts.done, 2);
});

test("getGraphStateSummary_complete_when_all_terminal", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistGraphNode(db, "g1", {
		id: "n1",
		kind: "unit",
		unitId: "U1",
		state: "done",
	});
	persistGraphNode(db, "g1", {
		id: "n2",
		kind: "unit",
		unitId: "U2",
		state: "failed",
	});

	const summary = getGraphStateSummary(db, "g1");
	assert.equal(summary.progress, 100);
	assert.equal(summary.isComplete, true);
});

// ─── Progress Stream ───────────────────────────────────────────────────────

test("persistProgressEvent_inserts_event", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistProgressEvent(db, "g1", {
		type: "node-start",
		nodeId: "n1",
		ts: new Date().toISOString(),
	});

	const events = getGraphProgressStream(db, "g1", 10);
	assert.equal(events.length, 1);
	assert.equal(events[0].type, "node-start");
	assert.equal(events[0].node_id, "n1");
});

test("getGraphProgressStream_returns_recent_first", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistProgressEvent(db, "g1", { type: "first", ts: "2024-01-01T00:00:00Z" });
	persistProgressEvent(db, "g1", {
		type: "second",
		ts: "2024-01-01T00:00:01Z",
	});

	const events = getGraphProgressStream(db, "g1", 10);
	assert.equal(events[0].type, "second");
	assert.equal(events[1].type, "first");
});

test("getGraphProgressStream_parses_payload_json", () => {
	const db = makeDb();
	ensureExecutionGraphSchema(db);

	persistProgressEvent(db, "g1", {
		type: "node-complete",
		nodeId: "n1",
		payload: JSON.stringify({ extra: "data" }),
		ts: new Date().toISOString(),
	});

	const events = getGraphProgressStream(db, "g1", 10);
	assert.equal(events[0].payload.extra, "data");
});
