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

const repository = vi.hoisted(() => ({
  prefetchMediaForSendResponse: vi.fn(async () => ({
    success: true as const,
    data: undefined,
  })),
  sendMessage: vi.fn(),
  unlockHistory: vi.fn(),
}));

const historySync = vi.hoisted(() => ({
  readAndSyncHistory: vi.fn(),
  readLocalHistorySnapshot: vi.fn(async () => ({
    messages: [],
    localCount: 0,
  })),
  resolveHistoryCacheIdentity: vi.fn(async () => null),
  syncNetworkHistory: vi.fn(),
}));

vi.mock("@/data/repositories/chat_repository_loader", () => ({
  loadChatRepository: vi.fn(async () => repository),
}));

vi.mock("@/data/repositories/chat_cache_identity", () => ({
  resolveChatConversationKey: vi.fn(async () => ({
    success: true,
    data: "user:test::character:elio",
  })),
}));

vi.mock("@/stores/chat/chat-history-sync", () => historySync);

import { loadHistoryActor } from "@/stores/chat/machine/actors/history";
import { sendMessageHttpActor } from "@/stores/chat/machine/actors/send";
import { unlockHistoryActor } from "@/stores/chat/machine/actors/unlock";

describe("chat actor request cancellation", () => {
  beforeEach(() => {
    repository.sendMessage.mockReset();
    repository.unlockHistory.mockReset();
    historySync.syncNetworkHistory.mockReset();
  });

  it("aborts an in-flight send request when its actor stops", async () => {
    repository.sendMessage.mockImplementation(() => new Promise(() => undefined));
    const actor = createActor(sendMessageHttpActor, {
      input: { characterId: "elio", content: "hello" },
    }).start();
    await vi.waitFor(() => expect(repository.sendMessage).toHaveBeenCalled());

    const signal = getSignal(repository.sendMessage.mock.calls[0]?.[2]);
    expect(signal.aborted).toBe(false);
    actor.stop();
    expect(signal.aborted).toBe(true);
  });

  it("aborts an in-flight history request when its callback actor stops", async () => {
    historySync.syncNetworkHistory.mockImplementation(
      () => new Promise(() => undefined),
    );
    const actor = createActor(loadHistoryActor, {
      input: {
        characterId: "elio",
        emptyChatGreeting: "Hello from Elio",
      },
    }).start();
    await vi.waitFor(() =>
      expect(historySync.syncNetworkHistory).toHaveBeenCalled(),
    );

    const historyCall = historySync.syncNetworkHistory.mock.calls[0];
    expect(historyCall?.[4]).toBe("Hello from Elio");
    const signal = getSignal(historyCall?.[5]);
    expect(signal.aborted).toBe(false);
    actor.stop();
    expect(signal.aborted).toBe(true);
  });

  it("aborts an in-flight unlock request when its actor stops", async () => {
    repository.unlockHistory.mockImplementation(
      () => new Promise(() => undefined),
    );
    const actor = createActor(unlockHistoryActor, {
      input: {
        characterId: "elio",
        emptyChatGreeting: "Hello from Elio",
      },
    }).start();
    await vi.waitFor(() => expect(repository.unlockHistory).toHaveBeenCalled());

    const signal = getSignal(repository.unlockHistory.mock.calls[0]?.[1]);
    expect(signal.aborted).toBe(false);
    actor.stop();
    expect(signal.aborted).toBe(true);
  });
});

function getSignal(options: unknown): AbortSignal {
  if (options instanceof AbortSignal) return options;
  const signal = (options as { signal?: AbortSignal } | undefined)?.signal;
  if (!signal) throw new Error("Missing AbortSignal");
  return signal;
}
