/**
 * LS7 — wire → domain normalization for the conversation transcript read, and the
 * `TauriConversationGateway.readPage` argument shape (single flat `request`).
 */
import { describe, it, expect, vi, beforeEach } from "vitest";

import { normalizeTurnPage } from "./conversationNormalization";

const invokeMock = vi.fn();
vi.mock("@tauri-apps/api/core", () => ({
  invoke: (...args: unknown[]) => invokeMock(...args),
}));

describe("normalizeTurnPage", () => {
  it("maps a full camelCase TurnPageDto to the domain TurnPage", () => {
    const page = normalizeTurnPage({
      turns: [
        {
          id: "t1",
          atMs: 1_700_000_000_000,
          role: "prompt",
          source: { kind: "human" },
          text: "bonjour",
          textLen: 7,
        },
        {
          id: "t2",
          atMs: 1_700_000_000_500,
          role: "response",
          source: { kind: "agent", agentId: "agent-7" },
          text: "réponse complète",
          textLen: 16,
        },
        {
          id: "t3",
          atMs: 1_700_000_001_000,
          role: "toolActivity",
          source: { kind: "agent", agentId: "agent-7" },
          text: "tool run",
          textLen: 8,
        },
      ],
      hasMore: true,
      nextAnchor: "t3",
    });

    expect(page.hasMore).toBe(true);
    expect(page.nextAnchor).toBe("t3");
    expect(page.turns).toHaveLength(3);
    expect(page.turns[0]).toEqual({
      id: "t1",
      atMs: 1_700_000_000_000,
      role: "prompt",
      source: { kind: "human" },
      text: "bonjour",
      textLen: 7,
    });
    // source.kind mapping: agent carries the agentId.
    expect(page.turns[1].source).toEqual({ kind: "agent", agentId: "agent-7" });
    // The three roles round-trip verbatim.
    expect(page.turns.map((t) => t.role)).toEqual([
      "prompt",
      "response",
      "toolActivity",
    ]);
  });

  it("preserves the full (unbounded) turn text", () => {
    const big = "Z".repeat(40_000);
    const page = normalizeTurnPage({
      turns: [{ id: "t1", atMs: 1, role: "response", source: { kind: "human" }, text: big, textLen: 40_000 }],
      hasMore: false,
    });
    expect(page.turns[0].text).toHaveLength(40_000);
    expect(page.turns[0].textLen).toBe(40_000);
  });

  it("omits nextAnchor when absent and defaults hasMore to false", () => {
    const page = normalizeTurnPage({ turns: [] });
    expect(page.turns).toEqual([]);
    expect(page.hasMore).toBe(false);
    expect("nextAnchor" in page).toBe(false);
  });

  it("degrades defensively on a malformed payload (never throws)", () => {
    const page = normalizeTurnPage({
      turns: [{ id: 42, role: "weird", source: { kind: "ghost" } }, null, "nope"],
      hasMore: "yes",
      nextAnchor: 99,
    });
    // hasMore only true on a strict boolean; non-string nextAnchor dropped.
    expect(page.hasMore).toBe(false);
    expect("nextAnchor" in page).toBe(false);
    // Unknown role ⇒ "prompt"; non-record source ⇒ human; missing text ⇒ "".
    expect(page.turns[0].role).toBe("prompt");
    expect(page.turns[1].source).toEqual({ kind: "human" });
    expect(page.turns[0].text).toBe("");
    // An unknown source.kind degrades to human.
    expect(page.turns[0].source).toEqual({ kind: "human" });
  });

  it("returns an empty page for a non-object payload", () => {
    expect(normalizeTurnPage(null)).toEqual({ turns: [], hasMore: false });
    expect(normalizeTurnPage(undefined)).toEqual({ turns: [], hasMore: false });
  });
});

describe("TauriConversationGateway.readPage", () => {
  beforeEach(() => {
    invokeMock.mockReset();
  });

  it("invokes read_conversation_page with a single flat request, default direction backward", async () => {
    invokeMock.mockResolvedValue({ turns: [], hasMore: false });
    const { TauriConversationGateway } = await import("./conversation");
    const gw = new TauriConversationGateway();

    await gw.readPage("proj-1", "conv-1");

    expect(invokeMock).toHaveBeenCalledTimes(1);
    const [command, args] = invokeMock.mock.calls[0];
    expect(command).toBe("read_conversation_page");
    // Single `request` argument with FLAT fields (no nested `cursor`).
    expect(args).toEqual({
      request: {
        projectId: "proj-1",
        conversationId: "conv-1",
        anchor: undefined,
        direction: "backward",
        limit: undefined,
      },
    });
  });

  it("forwards anchor/direction/limit and normalizes the result", async () => {
    invokeMock.mockResolvedValue({
      turns: [{ id: "t9", atMs: 5, role: "response", source: { kind: "human" }, text: "ok", textLen: 2 }],
      hasMore: true,
      nextAnchor: "t9",
    });
    const { TauriConversationGateway } = await import("./conversation");
    const gw = new TauriConversationGateway();

    const page = await gw.readPage("p", "c", {
      anchor: "t5",
      direction: "forward",
      limit: 25,
    });

    expect(invokeMock.mock.calls[0][1]).toEqual({
      request: {
        projectId: "p",
        conversationId: "c",
        anchor: "t5",
        direction: "forward",
        limit: 25,
      },
    });
    // Result is normalized to the domain TurnPage.
    expect(page.turns[0].id).toBe("t9");
    expect(page.nextAnchor).toBe("t9");
    expect(page.hasMore).toBe(true);
  });
});
