/**
 * L1 — each mock gateway satisfies its port interface (typecheck via the typed
 * `Gateways` binding below) and behaves sensibly offline.
 */
import { describe, it, expect } from "vitest";

import type { DomainEvent } from "@/domain";
import type { Gateways } from "@/ports";
import { createMockGateways, MockSystemGateway } from "./index";

// Typechecking this assignment proves every mock implements its port.
const gateways: Gateways = createMockGateways();

describe("createMockGateways", () => {
  it("exposes all gateways", () => {
    expect(Object.keys(gateways).sort()).toEqual([
      "agent",
      "conversation",
      "desktopServer",
      "device",
      "embedder",
      "focusedProject",
      "git",
      "input",
      "layout",
      "memory",
      "modelServer",
      "permission",
      "plugin",
      "pluginConfig",
      "pluginEvents",
      "pluginStorage",
      "pluginTask",
      "pluginToolchain",
      "pluginWorkspace",
      "profile",
      "project",
      "remote",
      "skill",
      "system",
      "template",
      "terminal",
      "ticket",
      "uiPreferences",
      "window",
      "workState",
    ]);
  });
});

describe("MockSystemGateway", () => {
  it("health returns an alive report echoing the note", async () => {
    const sys = new MockSystemGateway();
    const report = await sys.health("ping");
    expect(report.alive).toBe(true);
    expect(report.note).toBe("ping");
    expect(typeof report.version).toBe("string");
    expect(typeof report.timeMillis).toBe("number");
    expect(typeof report.correlationId).toBe("string");
  });

  it("health note defaults to null when omitted", async () => {
    const report = await new MockSystemGateway().health();
    expect(report.note).toBeNull();
  });

  it("delivers emitted domain events to subscribers and stops after unsubscribe", async () => {
    const sys = new MockSystemGateway();
    const received: DomainEvent[] = [];
    const unsub = await sys.onDomainEvent((e) => received.push(e));

    sys.emit({ type: "projectCreated", projectId: "p1" });
    expect(received).toHaveLength(1);
    expect(received[0]).toEqual({ type: "projectCreated", projectId: "p1" });

    unsub();
    sys.emit({ type: "projectCreated", projectId: "p2" });
    expect(received).toHaveLength(1);
  });

  it("health emits a smoke domain event to subscribers", async () => {
    const sys = new MockSystemGateway();
    const received: DomainEvent[] = [];
    await sys.onDomainEvent((e) => received.push(e));
    await sys.health();
    expect(received.some((e) => e.type === "projectCreated")).toBe(true);
  });
});

describe("other mock gateways", () => {
  it("project.createProject returns a project and listProjects reflects it", async () => {
    expect(await gateways.project.listProjects()).toEqual([]);
    const created = await gateways.project.createProject("n", "/root");
    expect(created.name).toBe("n");
    expect(created.root).toBe("/root");
    expect(typeof created.id).toBe("string");
  });

  it("terminal.openTerminal returns handles with incrementing session ids", async () => {
    const a = await gateways.terminal.openTerminal({ cwd: "/cwd", rows: 24, cols: 80 }, () => {});
    const b = await gateways.terminal.openTerminal({ cwd: "/cwd", rows: 24, cols: 80 }, () => {});
    expect(a.sessionId).not.toEqual(b.sessionId);
  });

  it("git.branches returns main as current branch", async () => {
    const b = await gateways.git.branches("p");
    expect(b.current).toBe("main");
    expect(b.branches).toContain("main");
  });
});
