/**
 * Contract tests for `TauriAgentGateway`: they assert the exact `invoke()`
 * payload shape, which the mock gateway (used by feature tests) does NOT
 * exercise. This guards the class of "works in mock, broken in the real app"
 * bugs — e.g. forgetting to nest `projectId` inside the command's `request` DTO.
 */

import { describe, it, expect, vi, beforeEach } from "vitest";

const invoke = vi.fn();
vi.mock("@tauri-apps/api/core", () => ({
  invoke: (...args: unknown[]) => invoke(...args),
  Channel: class {
    onmessage: ((c: number[]) => void) | null = null;
  },
}));

import { TauriAgentGateway } from "./agent";

describe("TauriAgentGateway invoke payloads", () => {
  beforeEach(() => invoke.mockReset().mockResolvedValue({}));

  it("create_agent nests projectId inside the request DTO", async () => {
    await new TauriAgentGateway().createAgent("proj-1", {
      name: "Backend",
      profileId: "prof-9",
    });
    expect(invoke).toHaveBeenCalledWith("create_agent", {
      request: {
        projectId: "proj-1",
        name: "Backend",
        profileId: "prof-9",
        initialContent: null,
      },
    });
  });

  it("update_agent_context wraps fields in the request DTO", async () => {
    await new TauriAgentGateway().updateContext("proj-1", "agent-2", "# ctx");
    expect(invoke).toHaveBeenCalledWith("update_agent_context", {
      request: { projectId: "proj-1", agentId: "agent-2", content: "# ctx" },
    });
  });

  it("list_agents / read / delete pass top-level args and unwrap read context DTO", async () => {
    const gw = new TauriAgentGateway();
    await gw.listAgents("p");
    expect(invoke).toHaveBeenCalledWith("list_agents", { projectId: "p" });

    invoke.mockResolvedValueOnce({ content: "# context" });
    await expect(gw.readContext("p", "a")).resolves.toBe("# context");
    expect(invoke).toHaveBeenCalledWith("read_agent_context", {
      projectId: "p",
      agentId: "a",
    });

    await gw.deleteAgent("p", "a");
    expect(invoke).toHaveBeenCalledWith("delete_agent", {
      projectId: "p",
      agentId: "a",
    });
  });

  it("attach_live_agent wraps project, agent and target node in the request DTO", async () => {
    invoke.mockResolvedValueOnce({
      agentId: "agent-2",
      nodeId: "node-3",
      sessionId: "session-4",
      kind: "pty",
    });
    const out = await new TauriAgentGateway().attachLiveAgent(
      "proj-1",
      "agent-2",
      "node-3",
    );
    expect(invoke).toHaveBeenCalledWith("attach_live_agent", {
      request: { projectId: "proj-1", agentId: "agent-2", nodeId: "node-3" },
    });
    expect(out.sessionId).toBe("session-4");
  });

  it("stop_live_agent wraps project and agent in the request DTO", async () => {
    invoke.mockResolvedValueOnce({
      agentId: "agent-2",
      sessionId: "session-4",
      kind: "pty",
    });
    const out = await new TauriAgentGateway().stopLiveAgent("proj-1", "agent-2");
    expect(invoke).toHaveBeenCalledWith("stop_live_agent", {
      request: { projectId: "proj-1", agentId: "agent-2" },
    });
    expect(out.sessionId).toBe("session-4");
  });

  it("change_agent_profile wraps all five fields in the request DTO (camelCase)", async () => {
    invoke.mockResolvedValueOnce({ agent: { id: "agent-2" } });
    await new TauriAgentGateway().changeAgentProfile(
      "proj-1",
      "agent-2",
      "prof-9",
      30,
      120,
    );
    expect(invoke).toHaveBeenCalledWith("change_agent_profile", {
      request: {
        projectId: "proj-1",
        agentId: "agent-2",
        profileId: "prof-9",
        rows: 30,
        cols: 120,
      },
    });
  });

  it("change_agent_profile returns the mutated agent and the relaunched session", async () => {
    const response = {
      agent: { id: "agent-2", profileId: "prof-9" },
      relaunchedSession: { sessionId: "sess-7", cwd: "/p", rows: 30, cols: 120 },
    };
    invoke.mockResolvedValueOnce(response);
    const out = await new TauriAgentGateway().changeAgentProfile(
      "proj-1",
      "agent-2",
      "prof-9",
      30,
      120,
    );
    expect(out.agent.profileId).toBe("prof-9");
    expect(out.relaunchedSession?.sessionId).toBe("sess-7");
  });

  it("change_agent_profile leaves relaunchedSession undefined when the backend omits it", async () => {
    invoke.mockResolvedValueOnce({ agent: { id: "agent-2" } });
    const out = await new TauriAgentGateway().changeAgentProfile(
      "proj-1",
      "agent-2",
      "prof-9",
      30,
      120,
    );
    expect(out.relaunchedSession).toBeUndefined();
  });

  it("update_agent_effort wraps the nullable effort override in the request DTO", async () => {
    invoke.mockResolvedValueOnce({ id: "agent-2", effort: { kind: "preset", value: "high" } });
    await new TauriAgentGateway().updateAgentEffort("proj-1", "agent-2", {
      kind: "preset",
      value: "high",
    });
    expect(invoke).toHaveBeenCalledWith("update_agent_effort", {
      request: {
        projectId: "proj-1",
        agentId: "agent-2",
        effort: { kind: "preset", value: "high" },
      },
    });

    invoke.mockClear().mockResolvedValueOnce({ id: "agent-2" });
    await new TauriAgentGateway().updateAgentEffort("proj-1", "agent-2", null);
    expect(invoke).toHaveBeenCalledWith("update_agent_effort", {
      request: { projectId: "proj-1", agentId: "agent-2", effort: null },
    });
  });
});
