/**
 * uok-coordination-store.test.mjs — SQLite-backed UOK coordination contracts.
 *
 * Purpose: prove UOK has Redis-like durable primitives without requiring a
 * Redis server: TTL keys, append-only streams, and lease-based queues.
 */

import assert from "node:assert/strict";
import { afterEach, test } from "vitest";
import { closeDatabase, getDatabase, openDatabase } from "../sf-db.js";
import {
	ensureCoordinationSchema,
	UokCoordinationStore,
} from "../uok/coordination-store.js";

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

function makeStore(now = () => 1_000) {
	assert.equal(openDatabase(":memory:"), true);
	const db = getDatabase();
	return new UokCoordinationStore(db, { now });
}

test("ensureCoordinationSchema_creates_kv_stream_and_queue_tables", () => {
	assert.equal(openDatabase(":memory:"), true);
	const db = getDatabase();
	ensureCoordinationSchema(db);
	const tables = db
		.prepare("SELECT name FROM sqlite_master WHERE type='table'")
		.all()
		.map((r) => r.name);
	assert.ok(tables.includes("uok_kv"));
	assert.ok(tables.includes("uok_stream_entries"));
	assert.ok(tables.includes("uok_queue_items"));
});

test("kv_set_get_and_expire_behaves_like_ttl_store", () => {
	let now = 1_000;
	const store = makeStore(() => now);

	store.set("lock:unit", { owner: "agent-a" }, { ttlMs: 100 });
	assert.deepEqual(store.get("lock:unit"), { owner: "agent-a" });

	now = 1_101;
	assert.equal(store.get("lock:unit"), null);
});

test("kv_cleanupExpired_removes_expired_rows", () => {
	let now = 1_000;
	const store = makeStore(() => now);
	store.set("a", 1, { ttlMs: 1 });
	store.set("b", 2);

	now = 1_002;
	assert.equal(store.cleanupExpired(), 1);
	assert.equal(store.get("a"), null);
	assert.equal(store.get("b"), 2);
});

test("kv_entries_when_prefix_matches_returns_live_decoded_rows", () => {
	let now = 1_000;
	const store = makeStore(() => now);
	store.set("parallel:intent:M001", { milestoneId: "M001" });
	store.set("parallel:intent:M002", { milestoneId: "M002" }, { ttlMs: 1 });
	store.set("skills:auto-create:last-run", { lastRunMs: 1 });

	now = 1_002;
	const entries = store.entries("parallel:intent:");

	assert.deepEqual(
		entries.map((entry) => entry.value.milestoneId),
		["M001"],
	);
});

test("streams_append_and_read_after_id_in_order", () => {
	const store = makeStore();
	const first = store.xadd("events", "node-start", { id: "a" });
	const second = store.xadd("events", "node-complete", { id: "a" });

	assert.equal(first, 1);
	assert.equal(second, 2);
	assert.deepEqual(
		store.xread("events", { afterId: 1 }).map((e) => e.type),
		["node-complete"],
	);
	assert.deepEqual(
		store.xread("events").map((e) => e.payload.id),
		["a", "a"],
	);
});

test("queue_claim_ack_and_release_enforces_owner_leases", () => {
	let now = 1_000;
	const store = makeStore(() => now);
	store.enqueue("work", "task-1", { unitId: "T01" });

	const claimed = store.claim("work", "agent-a", { leaseMs: 50 });
	assert.equal(claimed.id, "task-1");
	assert.equal(claimed.leaseOwner, "agent-a");
	assert.equal(store.claim("work", "agent-b"), null);
	assert.equal(store.ack("work", "task-1", "agent-b"), false);
	assert.equal(store.release("work", "task-1", "agent-a"), true);

	const reclaimed = store.claim("work", "agent-b", { leaseMs: 50 });
	assert.equal(reclaimed.leaseOwner, "agent-b");
	assert.equal(store.ack("work", "task-1", "agent-b"), true);
	assert.equal(store.peekQueueItem("work", "task-1").status, "done");

	now = 2_000;
	assert.equal(store.reapExpiredLeases("work"), 0);
});

test("queue_reapExpiredLeases_returns_stale_leased_work_to_ready", () => {
	let now = 1_000;
	const store = makeStore(() => now);
	store.enqueue("work", "task-1", { unitId: "T01" });
	store.claim("work", "agent-a", { leaseMs: 10 });

	now = 1_011;
	assert.equal(store.reapExpiredLeases("work"), 1);
	const claimed = store.claim("work", "agent-b", { leaseMs: 10 });
	assert.equal(claimed.leaseOwner, "agent-b");
	assert.equal(claimed.attempts, 2);
});
