import { describe, expect, it } from "vitest";
import { createActor, fromCallback, waitFor } from "xstate";

import type { UiMessage } from "@/stores/chat/ui-message";
import type { ChatEvent } from "@/stores/chat/chat-events";
import { chatMachine } from "@/stores/chat/chat-machine";
import type {
  ChatHistoryActorInput,
  LoadMoreHistoryActorEvent,
} from "@/stores/chat/machine/actors/history";

import {
  createLoadHistoryCallback,
  createTestChatMachine,
  TEST_CHAT_MACHINE_INPUT,
} from "./chat-machine.test-utils";

describe("chat history flow", () => {
  it("enters user ready after history is loaded", async () => {
    const actor = createActor(createTestChatMachine(), {
      input: TEST_CHAT_MACHINE_INPUT,
    }).start();

    actor.send({ type: "ChatUserLogin", token: "token" });
    await waitFor(actor, (snapshot) =>
      snapshot.matches({ userSession: "ready" }),
    );

    const snapshot = actor.getSnapshot();
    expect(snapshot.context.historyLoaded).toBe(true);

    actor.stop();
  });

  it("renders local history before network history sync finishes", async () => {
    let resolveNetwork!: () => void;
    const networkReleased = new Promise<void>((resolve) => {
      resolveNetwork = resolve;
    });
    const localMessage: UiMessage = {
      displayId: "local-msg",
      content: "cached local message",
      isFromAI: true,
      date: "2026-07-02",
    };
    const networkMessage: UiMessage = {
      displayId: "network-msg",
      content: "fresh network message",
      isFromAI: true,
      date: "2026-07-02",
    };
    const machine = chatMachine.provide({
      actors: {
        loadHistory: fromCallback<
          ChatEvent,
          ChatHistoryActorInput
        >(({ sendBack }) => {
          sendBack({
            type: "ChatLocalHistoryLoaded",
            output: {
              messages: [localMessage],
              localCount: 1,
            },
          });
          void networkReleased.then(() => {
            sendBack({
              type: "ChatNetworkHistoryLoaded",
              output: {
                messages: [networkMessage],
                localDisplayIds: [localMessage.displayId],
                localOverwritten: true,
                localCount: 1,
                networkCount: 1,
                total: 1,
                limit: 50,
              },
            });
          });
          return () => undefined;
        }),
      },
    });
    const actor = createActor(machine, {
      input: TEST_CHAT_MACHINE_INPUT,
    }).start();

    actor.send({ type: "ChatUserLogin", token: "token" });
    await waitFor(actor, (snapshot) =>
      snapshot.matches({ userSession: "ready" }),
    );

    expect(actor.getSnapshot().context.messages).toMatchObject([
      { displayId: "local-msg", content: "cached local message" },
    ]);

    resolveNetwork();
    await waitFor(
      actor,
      (snapshot) => snapshot.context.messages[0]?.displayId === "network-msg",
    );

    expect(actor.getSnapshot().context.messages).toMatchObject([
      { displayId: "network-msg", content: "fresh network message" },
    ]);

    actor.stop();
  });

  it("loads older pages until total is exhausted and keeps existing messages", async () => {
    const requests: LoadMoreHistoryActorEvent[] = [];
    const latestMessage: UiMessage = {
      displayId: "latest",
      content: "current unlocked message",
      isFromAI: true,
      date: "2026-07-15",
    };
    const machine = chatMachine.provide({
      actors: {
        loadHistory: createLoadHistoryCallback(
          [latestMessage],
          [latestMessage],
          { total: 120, limit: 50 },
        ),
        loadMoreHistory: fromCallback<
          LoadMoreHistoryActorEvent,
          ChatHistoryActorInput
        >(
          ({ receive, sendBack }) => {
            receive((event) => {
              requests.push(event);
              if (event.offset === 50) {
                sendBack({
                  type: "ChatOlderHistoryLoaded",
                  output: {
                    messages: [
                      createHistoryMessage("older-50"),
                      { ...latestMessage, content: "stale duplicate" },
                    ],
                    offset: event.offset,
                    total: 120,
                    limit: 50,
                  },
                });
                return;
              }
              sendBack({
                type: "ChatOlderHistoryLoaded",
                output: {
                  messages: [createHistoryMessage("oldest-100")],
                  offset: event.offset,
                  total: 120,
                  limit: 50,
                },
              });
            });
            return () => undefined;
          },
        ),
      },
    });
    const actor = createActor(machine, {
      input: TEST_CHAT_MACHINE_INPUT,
    }).start();

    actor.send({ type: "ChatUserLogin", token: "token" });
    await waitFor(actor, (snapshot) =>
      snapshot.matches({ userSession: "ready" }),
    );

    actor.send({ type: "ChatLoadMoreHistoryRequested" });
    await waitFor(
      actor,
      (snapshot) => snapshot.context.nextHistoryOffset === 100,
    );
    expect(requests[0]).toMatchObject({ offset: 50, limit: 50 });
    expect(actor.getSnapshot().context.messages).toMatchObject([
      { displayId: "older-50" },
      { displayId: "latest", content: "current unlocked message" },
    ]);

    actor.send({ type: "ChatLoadMoreHistoryRequested" });
    await waitFor(
      actor,
      (snapshot) => snapshot.context.nextHistoryOffset === 150,
    );
    actor.send({ type: "ChatLoadMoreHistoryRequested" });

    expect(requests).toHaveLength(2);
    expect(actor.getSnapshot().context.nextHistoryOffset).toBeGreaterThanOrEqual(
      actor.getSnapshot().context.historyTotal,
    );
    actor.stop();
  });

  it("keeps the offset after a failed page and allows retrying", async () => {
    let requestCount = 0;
    const machine = chatMachine.provide({
      actors: {
        loadHistory: createLoadHistoryCallback([], [], {
          total: 75,
          limit: 50,
        }),
        loadMoreHistory: fromCallback<
          LoadMoreHistoryActorEvent,
          ChatHistoryActorInput
        >(
          ({ receive, sendBack }) => {
            receive((event) => {
              requestCount += 1;
              if (requestCount === 1) {
                sendBack({
                  type: "ChatOlderHistoryLoadFailed",
                  error: new Error("network failed"),
                });
                return;
              }
              sendBack({
                type: "ChatOlderHistoryLoaded",
                output: {
                  messages: [createHistoryMessage("retry-message")],
                  offset: event.offset,
                  total: 75,
                  limit: 50,
                },
              });
            });
            return () => undefined;
          },
        ),
      },
    });
    const actor = createActor(machine, {
      input: TEST_CHAT_MACHINE_INPUT,
    }).start();

    actor.send({ type: "ChatGuestLogin" });
    await waitFor(actor, (snapshot) =>
      snapshot.matches({ guestSession: "ready" }),
    );
    actor.send({ type: "ChatLoadMoreHistoryRequested" });
    await waitFor(
      actor,
      (snapshot) =>
        requestCount === 1 && !snapshot.context.isLoadingMoreHistory,
    );
    expect(actor.getSnapshot().context.nextHistoryOffset).toBe(50);

    actor.send({ type: "ChatLoadMoreHistoryRequested" });
    await waitFor(
      actor,
      (snapshot) => snapshot.context.nextHistoryOffset === 100,
    );
    expect(requestCount).toBe(2);
    actor.stop();
  });

  it("does not request another page when total does not exceed limit", async () => {
    let requested = false;
    const machine = chatMachine.provide({
      actors: {
        loadHistory: createLoadHistoryCallback([], [], {
          total: 50,
          limit: 50,
        }),
        loadMoreHistory: fromCallback<
          LoadMoreHistoryActorEvent,
          ChatHistoryActorInput
        >(
          ({ receive }) => {
            receive(() => {
              requested = true;
            });
            return () => undefined;
          },
        ),
      },
    });
    const actor = createActor(machine, {
      input: TEST_CHAT_MACHINE_INPUT,
    }).start();

    actor.send({ type: "ChatUserLogin", token: "token" });
    await waitFor(actor, (snapshot) =>
      snapshot.matches({ userSession: "ready" }),
    );
    actor.send({ type: "ChatLoadMoreHistoryRequested" });

    expect(requested).toBe(false);
    expect(actor.getSnapshot().context.isLoadingMoreHistory).toBe(false);
    actor.stop();
  });
});

function createHistoryMessage(displayId: string): UiMessage {
  return {
    displayId,
    content: `Message ${displayId}`,
    isFromAI: true,
    date: "2026-07-15",
  };
}
