/**
 * swarm.test.mjs — Full test suite for the SF swarm system.
 *
 * Purpose: verify PersistentAgent identity/messaging, AgentSwarm registry/routing,
 * createDefaultSwarm topology, and SwarmDispatchLayer envelope routing using real
 * SQLite in isolated tmp directories (no mocking).
 *
 * Consumer: CI unit-test suite (`npm run test:unit`).
 */

import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { _clearSfRootCache } from "../paths.js";
import { closeDatabase } from "../sf-db.js";
import { AgentSwarm } from "../uok/agent-swarm.js";
import { PersistentAgent } from "../uok/persistent-agent.js";
import { SwarmDispatchLayer } from "../uok/swarm-dispatch.js";
import { createDefaultSwarm } from "../uok/swarm-roles.js";

// ─── Shared Setup ─────────────────────────────────────────────────────────────

const tmpRoots = [];

afterEach(() => {
	closeDatabase();
	_clearSfRootCache();
	for (const root of tmpRoots.splice(0)) {
		rmSync(root, { recursive: true, force: true });
	}
});

function makeProject() {
	const root = mkdtempSync(join(tmpdir(), "sf-swarm-"));
	tmpRoots.push(root);
	return root;
}

// ─── PersistentAgent — identity ───────────────────────────────────────────────

describe("PersistentAgent — identity", () => {
	test("init_when_new_creates_stable_agentId", () => {
		const root = makeProject();
		const agent = new PersistentAgent(root, { name: "alice", role: "worker" });
		expect(agent.identity.agentId).toMatch(/^agent-/);
		expect(agent.identity.name).toBe("alice");
		expect(agent.identity.role).toBe("worker");
	});

	test("init_when_resumed_restores_same_agentId", () => {
		const root = makeProject();
		const agent1 = new PersistentAgent(root, { name: "bob", role: "worker" });
		const originalId = agent1.identity.agentId;

		// Simulate process restart
		closeDatabase();
		_clearSfRootCache();

		const agent2 = new PersistentAgent(root, { name: "bob", role: "worker" });
		expect(agent2.identity.agentId).toBe(originalId);
	});

	test("coreBlock_when_set_persists_across_restart", () => {
		const root = makeProject();
		const agent1 = new PersistentAgent(root, { name: "carol", role: "worker" });
		agent1.setCoreBlock("notes", { text: "hello world" });

		// Simulate process restart
		closeDatabase();
		_clearSfRootCache();

		const agent2 = new PersistentAgent(root, { name: "carol", role: "worker" });
		expect(agent2.getCoreBlock("notes")).toEqual({ text: "hello world" });
	});
});

// ─── PersistentAgent — messaging ──────────────────────────────────────────────

describe("PersistentAgent — messaging", () => {
	test("send_receive_delivers_message", () => {
		const root = makeProject();
		const agentA = new PersistentAgent(root, {
			name: "sender",
			role: "worker",
		});
		const agentB = new PersistentAgent(root, {
			name: "receiver",
			role: "worker",
		});

		agentA.send("receiver", "hello-world-payload");

		// Force inbox refresh (bypassing the 30s interval guard)
		agentB.inbox.refresh();
		const msgs = agentB.receive(true);

		expect(msgs.length).toBeGreaterThanOrEqual(1);
		const found = msgs.find((m) => m.body === "hello-world-payload");
		expect(found).toBeDefined();
	});

	test("markRead_when_called_hides_from_unread", () => {
		const root = makeProject();
		const agentA = new PersistentAgent(root, {
			name: "sender2",
			role: "worker",
		});
		const agentB = new PersistentAgent(root, {
			name: "receiver2",
			role: "worker",
		});

		agentA.send("receiver2", "ping");

		agentB.inbox.refresh();
		const unread = agentB.receive(true);
		expect(unread.length).toBeGreaterThanOrEqual(1);

		agentB.markRead(unread[0].id);

		agentB.inbox.refresh();
		expect(agentB.receive(true)).toHaveLength(0);
	});

	test("broadcast_delivers_to_all_recipients", () => {
		const root = makeProject();
		const agentA = new PersistentAgent(root, {
			name: "broadcaster",
			role: "coordinator",
		});
		const agentB = new PersistentAgent(root, {
			name: "recv-b",
			role: "worker",
		});
		const agentC = new PersistentAgent(root, {
			name: "recv-c",
			role: "worker",
		});

		agentA.broadcast("announcement", {}, ["recv-b", "recv-c"]);

		agentB.inbox.refresh();
		agentC.inbox.refresh();

		expect(agentB.receive(true)).toHaveLength(1);
		expect(agentC.receive(true)).toHaveLength(1);
	});
});

// ─── AgentSwarm — registry ────────────────────────────────────────────────────

describe("AgentSwarm — registry", () => {
	test("register_adds_agent_to_roster", () => {
		const root = makeProject();
		const swarm = new AgentSwarm(root, { name: "test-swarm" });
		const worker = new PersistentAgent(root, {
			name: "worker",
			role: "worker",
		});

		swarm.register(worker);

		expect(swarm.getAll()).toHaveLength(1);
		expect(swarm.get("worker").identity.name).toBe("worker");
	});

	test("getByRole_filters_correctly", () => {
		const root = makeProject();
		const swarm = new AgentSwarm(root, { name: "test-swarm-roles" });
		const coord = new PersistentAgent(root, {
			name: "coord",
			role: "coordinator",
		});
		const w1 = new PersistentAgent(root, { name: "w1", role: "worker" });
		const w2 = new PersistentAgent(root, { name: "w2", role: "worker" });

		swarm.register(coord);
		swarm.register(w1);
		swarm.register(w2);

		expect(swarm.getByRole("worker")).toHaveLength(2);
		expect(swarm.getByRole("coordinator")).toHaveLength(1);
	});

	test("persist_and_load_round_trips", () => {
		const root = makeProject();
		const swarm = new AgentSwarm(root, { name: "persist-swarm" });
		const wA = new PersistentAgent(root, { name: "wA", role: "worker" });
		const wB = new PersistentAgent(root, { name: "wB", role: "worker" });

		swarm.register(wA);
		swarm.register(wB);
		swarm.persist();

		// Simulate restart
		closeDatabase();
		_clearSfRootCache();

		const loaded = AgentSwarm.load(root, "persist-swarm");
		const names = loaded
			.getAll()
			.map((a) => a.identity.name)
			.sort();
		expect(names).toEqual(["wA", "wB"]);
	});

	test("agentDirectory_injected_on_register", () => {
		const root = makeProject();
		const swarm = new AgentSwarm(root, { name: "dir-swarm" });
		const a1 = new PersistentAgent(root, { name: "dir-a1", role: "worker" });
		const a2 = new PersistentAgent(root, { name: "dir-a2", role: "worker" });

		swarm.register(a1);
		swarm.register(a2);

		const dir1 = JSON.parse(a1.getCoreBlock("agent_directory"));
		const dir2 = JSON.parse(a2.getCoreBlock("agent_directory"));
		const names1 = dir1.map((e) => e.name);
		const names2 = dir2.map((e) => e.name);

		expect(names1).toContain("dir-a1");
		expect(names1).toContain("dir-a2");
		expect(names2).toContain("dir-a1");
		expect(names2).toContain("dir-a2");
	});
});

// ─── createDefaultSwarm — topology ────────────────────────────────────────────

describe("createDefaultSwarm — topology", () => {
	test("creates_nine_agents", async () => {
		const root = makeProject();
		const { swarm } = await createDefaultSwarm(root);
		expect(swarm.getAll()).toHaveLength(9);
	});

	test("topology_has_correct_roles", async () => {
		const root = makeProject();
		const { swarm } = await createDefaultSwarm(root);
		const topology = swarm.getTopology();

		expect(topology.coordinator).toHaveLength(1);
		expect(topology.workers).toHaveLength(2);
		expect(topology.scouts).toHaveLength(1);
		expect(topology.reviewers).toHaveLength(1);
		expect(topology.planners).toHaveLength(1);
		expect(topology.verifiers).toHaveLength(1);
		expect(topology.scribes).toHaveLength(1);
		expect(topology.adversaries).toHaveLength(1);
	});
});

// ─── SwarmDispatchLayer — routing ─────────────────────────────────────────────

describe("SwarmDispatchLayer — routing", () => {
	test("dispatch_build_envelope_routes_to_worker", async () => {
		const root = makeProject();
		const layer = new SwarmDispatchLayer(root);

		const result = await layer.dispatch({
			unitId: "task-build-1",
			unitType: "task",
			workMode: "build",
			payload: "build task data",
			priority: 5,
			scope: "test-scope",
		});

		expect(result.messageId).toBeTruthy();
		expect(result.targetAgent).toBeTruthy();

		const swarm = await layer.getOrCreateSwarm();
		const agent = swarm.get(result.targetAgent);
		expect(agent).toBeDefined();
		expect(agent.identity.role).toBe("worker");
	});

	test("dispatch_review_envelope_routes_to_reviewer", async () => {
		const root = makeProject();
		const layer = new SwarmDispatchLayer(root);

		const result = await layer.dispatch({
			unitId: "task-review-1",
			unitType: "task",
			workMode: "review",
			payload: "review task data",
			priority: 5,
			scope: "test-scope",
		});

		expect(result.messageId).toBeTruthy();

		const swarm = await layer.getOrCreateSwarm();
		const agent = swarm.get(result.targetAgent);
		expect(agent).toBeDefined();
		expect(agent.identity.role).toBe("reviewer");
	});

	test("dispatch_when_targetAgent_set_routes_to_named_agent", async () => {
		const root = makeProject();
		const layer = new SwarmDispatchLayer(root);

		const result = await layer.dispatch({
			unitId: "task-target-1",
			unitType: "task",
			workMode: "build",
			targetAgent: "verifier",
			payload: "verify task data",
			priority: 5,
			scope: "test-scope",
		});

		expect(result.messageId).toBeTruthy();
		expect(result.targetAgent).toBe("verifier");

		const swarm = await layer.getOrCreateSwarm();
		const agent = swarm.get(result.targetAgent);
		expect(agent).toBeDefined();
		expect(agent.identity.role).toBe("verifier");
	});

	test("dispatchBatch_dispatches_all", async () => {
		const root = makeProject();
		const layer = new SwarmDispatchLayer(root);

		const envelopes = [
			{
				unitId: "t1",
				unitType: "task",
				workMode: "build",
				payload: "task-1",
				priority: 1,
				scope: "test",
			},
			{
				unitId: "t2",
				unitType: "task",
				workMode: "build",
				payload: "task-2",
				priority: 1,
				scope: "test",
			},
			{
				unitId: "t3",
				unitType: "task",
				workMode: "build",
				payload: "task-3",
				priority: 1,
				scope: "test",
			},
		];

		const results = await layer.dispatchBatch(envelopes);

		expect(results).toHaveLength(3);
		for (const r of results) {
			expect(r.messageId).toBeTruthy();
			expect(r.targetAgent).toBeTruthy();
		}
	});
});
