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

import {
  SendMessageRequestSchema,
  UnlockHistoryRequestSchema,
  UnlockPrivateRequestSchema,
} from "@/data/schemas/chat";

const httpClientMock = vi.hoisted(() => vi.fn());

vi.mock("../http_client", () => ({
  httpClient: httpClientMock,
}));

import { ChatApi } from "../chat_api";
import { CharacterApi } from "../character_api";
import { PrivateZoneApi } from "../private_zone_api";

const CHARACTER_ID = "elio";

describe("multi-character API contract", () => {
  beforeEach(() => {
    httpClientMock.mockReset();
  });

  it("sends characterId with chat messages", async () => {
    httpClientMock.mockResolvedValue({
      success: true,
      data: {
        reply: "Hello",
        messageId: "message-1",
        image: { type: null, url: null },
        lockDetail: { locked: false, reason: null },
      },
    });

    await new ChatApi().sendMessage(
      SendMessageRequestSchema.parse({
        characterId: CHARACTER_ID,
        message: "Hello",
      }),
    );

    expect(httpClientMock).toHaveBeenCalledWith("/api/chat/send", {
      method: "POST",
      body: expect.objectContaining({ characterId: CHARACTER_ID }),
    });
  });

  it("scopes history to characterId", async () => {
    httpClientMock.mockResolvedValue({
      success: true,
      data: { messages: [], total: 0, limit: 50, offset: 0 },
    });

    await new ChatApi().getHistory(CHARACTER_ID, 50, 10);

    expect(httpClientMock).toHaveBeenCalledWith("/api/chat/history", {
      query: { characterId: CHARACTER_ID, limit: 50, offset: 10 },
    });
  });

  it("reports an idempotent chat action event", async () => {
    const body = {
      eventId: "4b223884-02ec-4fd2-aacf-e84ee8ca3adb",
      actionId: "action-1",
      characterId: CHARACTER_ID,
      eventType: "opened" as const,
    };
    httpClientMock.mockResolvedValue({
      success: true,
      data: { ...body, duplicate: false },
    });

    await new ChatApi().recordActionEvent(body);

    expect(httpClientMock).toHaveBeenCalledWith("/api/chat/action-events", {
      method: "POST",
      body,
    });
  });

  it("persists the character opening message with the canonical contract", async () => {
    httpClientMock.mockResolvedValue({
      success: true,
      data: {
        messageId: "opening-message-1",
        created: true,
        openingMessage: "Hello from Elio",
        createdAt: "2026-07-23T00:00:00Z",
      },
    });

    await new ChatApi().saveOpeningMessage({
      characterId: CHARACTER_ID,
      openingMessage: "Hello from Elio",
    });

    expect(httpClientMock).toHaveBeenCalledWith(
      "/api/chat/opening-message",
      {
        method: "POST",
        body: {
          characterId: CHARACTER_ID,
          openingMessage: "Hello from Elio",
        },
      },
    );
  });

  it("loads the chat character catalog", async () => {
    httpClientMock.mockResolvedValue({
      success: true,
      data: { items: [], defaultCharacterId: "elio" },
    });

    await new CharacterApi().getChatCharacters();

    expect(httpClientMock).toHaveBeenCalledWith("/api/characters", {
      query: { capability: "chat" },
    });
  });

  it("loads chat previews", async () => {
    const api = new ChatApi();
    httpClientMock.mockResolvedValueOnce({
      success: true,
      data: { items: [] },
    });

    await api.getPreviews();

    expect(httpClientMock).toHaveBeenCalledWith("/api/chat/previews", {});
  });

  it("forwards request cancellation to the HTTP client", async () => {
    const controller = new AbortController();
    httpClientMock.mockResolvedValue({
      success: true,
      data: { messages: [], total: 0, limit: 50, offset: 0 },
    });

    await new ChatApi().getHistory(CHARACTER_ID, 50, 0, {
      signal: controller.signal,
    });

    expect(httpClientMock).toHaveBeenCalledWith("/api/chat/history", {
      query: { characterId: CHARACTER_ID, limit: 50, offset: 0 },
      signal: controller.signal,
    });
  });

  it("sends characterId with private and history unlocks", async () => {
    const api = new ChatApi();
    httpClientMock
      .mockResolvedValueOnce({
        success: true,
        data: {
          unlocked: false,
          reason: "not_found",
          image: { type: null, url: null },
        },
      })
      .mockResolvedValueOnce({
        success: true,
        data: { unlocked: false, reason: "no_locked_messages" },
      });

    await api.unlockPrivateMessage(
      UnlockPrivateRequestSchema.parse({
        characterId: CHARACTER_ID,
        messageId: "message-1",
      }),
    );
    await api.unlockHistory(
      UnlockHistoryRequestSchema.parse({ characterId: CHARACTER_ID }),
    );

    expect(httpClientMock).toHaveBeenNthCalledWith(
      1,
      "/api/chat/unlock-private",
      {
        method: "POST",
        body: expect.objectContaining({ characterId: CHARACTER_ID }),
      },
    );
    expect(httpClientMock).toHaveBeenNthCalledWith(
      2,
      "/api/chat/unlock-history",
      {
        method: "POST",
        body: { characterId: CHARACTER_ID },
      },
    );
  });

  it("scopes private-zone albums to characterId", async () => {
    httpClientMock.mockResolvedValue({
      success: true,
      data: { items: [], creditBalance: 0 },
    });

    await new PrivateZoneApi().getAlbums({
      characterId: CHARACTER_ID,
      limit: 20,
    });

    expect(httpClientMock).toHaveBeenCalledWith("/api/private-zone/albums", {
      query: { characterId: CHARACTER_ID, limit: 20 },
    });
  });
});
