import { beforeEach, describe, expect, it, vi } from "vitest";
import * as GateRegistryModule from "../uok/gate-registry.js";

vi.mock("../uok/gate-runner.js", () => {
	class UokGateRunner {
		constructor() {
			this.gates = new Map();
		}

		register(gate) {
			if (!gate?.id || typeof gate.execute !== "function") {
				throw new Error("UokGateRunner.register: invalid gate");
			}
			this.gates.set(gate.id, gate);
		}

		async run(gateId, ctx) {
			const gate = this.gates.get(gateId);
			if (!gate) throw new Error(`missing gate ${gateId}`);
			return gate.execute(ctx);
		}
	}

	return { UokGateRunner };
});

function stubGate(id, execute = async () => ({ passed: true, gateId: id })) {
	return {
		id,
		type: "verification",
		execute,
	};
}

describe("UokGateRegistry", () => {
	beforeEach(() => {
		GateRegistryModule.resetGateRegistryForTests();
	});

	it("register_has_list_when_two_gates_registered_returns_both_ids", () => {
		const registry = new GateRegistryModule.UokGateRegistry();

		registry.register(stubGate("alpha"));
		registry.register(stubGate("beta"));

		expect(registry.has("alpha")).toBe(true);
		expect(registry.has("beta")).toBe(true);
		expect(registry.list()).toEqual(["alpha", "beta"]);
	});

	it("register_when_gate_missing_id_throws", () => {
		const registry = new GateRegistryModule.UokGateRegistry();

		expect(() => registry.register({ type: "verification" })).toThrow(
			"gate.id required",
		);
	});

	it("run_when_gate_unknown_throws_with_gate_id", async () => {
		const registry = new GateRegistryModule.UokGateRegistry();

		await expect(registry.run("missing-gate")).rejects.toThrow("missing-gate");
	});

	it("run_when_gate_registered_calls_execute_and_returns_gate_result_shape", async () => {
		const registry = new GateRegistryModule.UokGateRegistry();
		const execute = vi.fn(async () => ({ passed: true, gateId: "alpha" }));
		registry.register(stubGate("alpha", execute));

		const result = await registry.run("alpha", { basePath: "/tmp/sf" });

		expect(execute).toHaveBeenCalledWith({ basePath: "/tmp/sf" });
		expect(result).toEqual({ passed: true, gateId: "alpha" });
	});

	it("singleton_when_reset_called_returns_new_instance", () => {
		const first = GateRegistryModule.getGateRegistry();
		const second = GateRegistryModule.getGateRegistry();

		GateRegistryModule.resetGateRegistryForTests();
		const third = GateRegistryModule.getGateRegistry();

		expect(second).toBe(first);
		expect(third).not.toBe(first);
	});

	it("run_when_ctx_factory_registered_merges_factory_and_override_context", async () => {
		const registry = new GateRegistryModule.UokGateRegistry();
		const execute = vi.fn(async (ctx) => ({
			passed: true,
			gateId: "alpha",
			ctx,
		}));
		registry.register(stubGate("alpha", execute), async () => ({ foo: 1 }));

		const result = await registry.run("alpha", { bar: 2 });

		expect(execute).toHaveBeenCalledWith({ foo: 1, bar: 2 });
		expect(result).toEqual({
			passed: true,
			gateId: "alpha",
			ctx: { foo: 1, bar: 2 },
		});
	});
});
