import { describe, it, expect, vi, beforeEach } from "vitest";
import { ExitGuardManagerImpl } from "./exit-guard-manager";

describe("ExitGuardManagerImpl", () => {
  let manager: ExitGuardManagerImpl;

  beforeEach(() => {
    manager = new ExitGuardManagerImpl();
  });

  it("canExit resolves true when no guards are registered", async () => {
    await expect(manager.canExit()).resolves.toBe(true);
  });

  it("canExit resolves true when every guard returns true", async () => {
    manager.register("a", async () => true);
    manager.register("b", async () => true);

    await expect(manager.canExit()).resolves.toBe(true);
  });

  it("canExit resolves false when any guard returns false", async () => {
    manager.register("a", async () => true);
    manager.register("b", async () => false);

    await expect(manager.canExit()).resolves.toBe(false);
  });

  it("canExit short-circuits and does not run guards after a false", async () => {
    const guardA = vi.fn(async () => false);
    const guardB = vi.fn(async () => true);

    manager.register("a", guardA);
    manager.register("b", guardB);

    await manager.canExit();

    expect(guardA).toHaveBeenCalled();
    expect(guardB).not.toHaveBeenCalled();
  });

  it("unregister removes the guard", async () => {
    const guard = vi.fn(async () => false);
    manager.register("a", guard);
    manager.unregister("a");

    await expect(manager.canExit()).resolves.toBe(true);
    expect(guard).not.toHaveBeenCalled();
  });

  it("register overwrites an existing guard with the same id", async () => {
    manager.register("a", async () => false);
    manager.register("a", async () => true);

    await expect(manager.canExit()).resolves.toBe(true);
  });

  it("swallows errors in guards and continues with the remaining ones", async () => {
    const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
    const failing = vi.fn(async () => {
      throw new Error("boom");
    });
    const allowing = vi.fn(async () => true);

    manager.register("a", failing);
    manager.register("b", allowing);

    await expect(manager.canExit()).resolves.toBe(true);
    expect(failing).toHaveBeenCalled();
    expect(allowing).toHaveBeenCalled();
    expect(consoleErrorSpy).toHaveBeenCalledWith("Exit guard failed:", expect.any(Error));

    consoleErrorSpy.mockRestore();
  });
});
