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

import type { ChatEvent } from "@/stores/chat/chat-events";
import { chatMachine } from "@/stores/chat/chat-machine";

import {
  createLoadHistoryCallback,
  createTestChatMachine,
  makeChatSendResponse,
  type SendMessageHttpOutput,
  type UnlockHistoryOutput,
} from "./chat-machine.test-utils";

describe("chat send flow", () => {
  it("allows multiple messages to be queued without leaving ready state", async () => {
    const actor = createActor(createTestChatMachine()).start();

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

    actor.send({ type: "ChatSendMessage", content: "hello" });
    actor.send({ type: "ChatSendMessage", content: "still there?" });

    expect(actor.getSnapshot().matches({ userSession: "ready" })).toBe(true);
    expect(actor.getSnapshot().context.messages).toMatchObject([
      { content: "hello", isFromAI: false },
      { content: "still there?", isFromAI: false },
    ]);

    await waitFor(
      actor,
      (snapshot) => snapshot.context.pendingReplyCount === 0,
    );
    expect(actor.getSnapshot().context.isReplyingAI).toBe(false);

    actor.stop();
  });

  it("applies direct HTTP actor output with a type-bound action", async () => {
    const actor = createActor(createTestChatMachine()).start();

    actor.send({ type: "ChatGuestLogin" });
    await waitFor(actor, (snapshot) =>
      snapshot.matches({ guestSession: "ready" }),
    );

    actor.send({
      type: "ChatSendImage",
      imageBase64: "data:image/png;base64,image",
    });
    await waitFor(actor, (snapshot) =>
      snapshot.matches({ guestSession: "ready" }) &&
      snapshot.context.pendingReplyCount === 0,
    );

    expect(actor.getSnapshot().context.isReplyingAI).toBe(false);
    expect(actor.getSnapshot().context.messages).toMatchObject([
      { content: "[Image]", isFromAI: false },
    ]);

    actor.stop();
  });

  it("restores ready state when the direct HTTP actor fails", async () => {
    const actor = createActor(
      createTestChatMachine({
        sendMessageHttpError: new Error("send failed"),
      }),
    ).start();

    actor.send({ type: "ChatGuestLogin" });
    await waitFor(actor, (snapshot) =>
      snapshot.matches({ guestSession: "ready" }),
    );

    actor.send({
      type: "ChatSendImage",
      imageBase64: "data:image/png;base64,image",
    });
    await waitFor(actor, (snapshot) =>
      snapshot.matches({ guestSession: "ready" }),
    );

    expect(actor.getSnapshot().context.isReplyingAI).toBe(false);

    actor.stop();
  });

  it("tracks replying state by queued batch instead of user message count", async () => {
    const machine = chatMachine.provide({
      actors: {
        loadHistory: createLoadHistoryCallback([]),
        sendMessageHttp: fromPromise<
          SendMessageHttpOutput,
          { content: string }
        >(async () => ({
          response: makeChatSendResponse(),
          reply: null,
        })),
        httpMessageQueue: fromCallback<ChatEvent>(({ receive, sendBack }) => {
          const pending: string[] = [];
          receive((event) => {
            if (event.type !== "ChatSendMessage") return;
            pending.push(event.content);
            if (pending.length !== 2) return;
            sendBack({ type: "ChatQueuedSendStarted" });
            sendBack({
              type: "ChatQueuedHttpDone",
              output: {
                response: makeChatSendResponse(),
                reply: null,
              },
            });
          });
          return () => undefined;
        }),
        unlockHistory: fromPromise<UnlockHistoryOutput>(async () => ({
          unlocked: true,
          reason: "ok",
          shortfallCredits: 0,
          messages: [],
        })),
      },
    });
    const actor = createActor(machine).start();

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

    actor.send({ type: "ChatSendMessage", content: "first" });
    actor.send({ type: "ChatSendMessage", content: "second" });

    await waitFor(
      actor,
      (snapshot) => snapshot.context.messages.length === 2,
    );

    expect(actor.getSnapshot().context.pendingReplyCount).toBe(0);
    expect(actor.getSnapshot().context.isReplyingAI).toBe(false);

    actor.stop();
  });
});
