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

import { ChatSendResponse } from "@/data/dto/chat";

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

describe("chat unlock flow", () => {
  it("prompts before unlocking multiple locked history messages after payment", async () => {
    const actor = createActor(
      createTestChatMachine({
        historyMessages: [
          {
            content: "",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "private_message",
          },
          {
            content: "",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "voice_message",
          },
        ],
      }),
    ).start();

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

    actor.send({ type: "ChatPaymentSucceeded" });

    expect(actor.getSnapshot().context.unlockHistoryPromptVisible).toBe(true);
    expect(actor.getSnapshot().context.lockedHistoryCount).toBe(2);

    actor.stop();
  });

  it("keeps a single locked image message locked after payment", async () => {
    const actor = createActor(
      createTestChatMachine({
        historyMessages: [
          {
            id: "msg-image-locked",
            content: "",
            isFromAI: true,
            date: "2026-06-29",
            imageUrl: "https://example.com/locked.jpg",
            imagePaywalled: true,
            locked: true,
            lockReason: "image",
          },
        ],
        unlockHistoryOutput: {
          unlocked: true,
          reason: "ok",
          shortfallCredits: 0,
          messages: [
            {
              id: "msg-image-locked",
              content: "",
              isFromAI: true,
              date: "2026-06-29",
              imageUrl: "https://example.com/unlocked.jpg",
              locked: false,
              lockReason: null,
            },
          ],
        },
      }),
    ).start();

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

    actor.send({ type: "ChatPaymentSucceeded" });

    expect(actor.getSnapshot().matches({ userSession: "ready" })).toBe(true);
    expect(actor.getSnapshot().context.unlockHistoryPromptVisible).toBe(false);
    expect(actor.getSnapshot().context.messages).toMatchObject([
      {
        id: "msg-image-locked",
        imageUrl: "https://example.com/locked.jpg",
        imagePaywalled: true,
        locked: true,
      },
    ]);

    actor.stop();
  });

  it("unlocks history after the prompt is confirmed", async () => {
    const actor = createActor(
      createTestChatMachine({
        historyMessages: [
          {
            content: "",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "private_message",
          },
          {
            content: "",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "voice_message",
          },
        ],
        unlockHistoryOutput: {
          unlocked: true,
          reason: "ok",
          shortfallCredits: 0,
          messages: [
            {
              content: "unlocked",
              isFromAI: true,
              date: "2026-06-29",
              locked: false,
              lockReason: null,
            },
          ],
        },
      }),
    ).start();

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

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

    expect(actor.getSnapshot().context.unlockHistoryPromptVisible).toBe(false);
    expect(actor.getSnapshot().context.messages).toMatchObject([
      { content: "unlocked", locked: false },
    ]);

    actor.stop();
  });

  it("restores the unlock prompt when the history actor fails", async () => {
    const actor = createActor(
      createTestChatMachine({
        historyMessages: [
          {
            content: "",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "private_message",
          },
          {
            content: "",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "voice_message",
          },
        ],
        unlockHistoryError: new Error("unlock failed"),
      }),
    ).start();

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

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

    expect(actor.getSnapshot().context).toMatchObject({
      isUnlockingHistory: false,
      unlockHistoryPromptVisible: true,
      unlockHistoryError: "Failed to unlock messages. Please try again.",
    });

    actor.stop();
  });

  it("unlocks a single private message without navigating to payment", async () => {
    const actor = createActor(
      createTestChatMachine({
        historyMessages: [
          {
            id: "msg-private-locked",
            content: "Original private message content.",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "private_message",
            lockedPrivate: true,
            privateMessageHint: "A private message is waiting.",
          },
        ],
        unlockMessageOutput: {
          messageId: "msg-private-locked",
          response: makeUnlockPrivateResponse({
            content: "Unlocked private message content.",
          }),
        },
      }),
    ).start();

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

    actor.send({
      type: "ChatUnlockMessageRequested",
      messageId: "msg-private-locked",
      kind: "private",
    });

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

    expect(actor.getSnapshot().context.unlockPaywallRequest).toBeNull();
    expect(actor.getSnapshot().context.isUnlockingMessage).toBe(false);
    expect(actor.getSnapshot().context.messages).toMatchObject([
      {
        id: "msg-private-locked",
        content: "Unlocked private message content.",
        locked: false,
        lockReason: null,
        lockedPrivate: false,
        privateMessageHint: null,
      },
    ]);

    actor.stop();
  });

  it("does not overwrite a user message when it shares the private message id", async () => {
    const actor = createActor(
      createTestChatMachine({
        historyMessages: [
          {
            id: "msg-shared-id",
            content: "User original question",
            isFromAI: false,
            date: "2026-06-29",
          },
          {
            id: "msg-shared-id",
            content: "Original AI private message",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "private_message",
            lockedPrivate: true,
            privateMessageHint: "A private message is waiting.",
          },
        ],
        unlockMessageOutput: {
          messageId: "msg-shared-id",
          response: makeUnlockPrivateResponse({
            content: "Unlocked private AI message.",
          }),
        },
      }),
    ).start();

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

    actor.send({
      type: "ChatUnlockMessageRequested",
      messageId: "msg-shared-id",
      kind: "private",
    });

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

    expect(actor.getSnapshot().context.messages).toMatchObject([
      {
        id: "msg-shared-id",
        content: "User original question",
        isFromAI: false,
      },
      {
        id: "msg-shared-id",
        content: "Unlocked private AI message.",
        isFromAI: true,
        locked: false,
        lockReason: null,
        lockedPrivate: false,
        privateMessageHint: null,
      },
    ]);

    actor.stop();
  });

  it("keeps image message text empty after a single image unlock succeeds", async () => {
    const actor = createActor(
      createTestChatMachine({
        historyMessages: [
          {
            id: "msg-image-locked",
            content: "",
            isFromAI: true,
            date: "2026-06-29",
            imageUrl: "https://example.com/locked.jpg",
            imagePaywalled: true,
            locked: true,
            lockReason: "image",
          },
        ],
        unlockMessageOutput: {
          messageId: "msg-image-locked",
          response: makeUnlockPrivateResponse({
            content: "This text should not render below the image.",
          }),
        },
      }),
    ).start();

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

    actor.send({
      type: "ChatUnlockMessageRequested",
      messageId: "msg-image-locked",
      kind: "image",
    });

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

    expect(actor.getSnapshot().context.messages).toMatchObject([
      {
        id: "msg-image-locked",
        content: "",
        imageUrl: "https://example.com/locked.jpg",
        imagePaywalled: false,
        locked: false,
        lockReason: null,
      },
    ]);

    actor.stop();
  });

  it("applies the real voice audio url after unlock succeeds", async () => {
    const actor = createActor(
      createTestChatMachine({
        historyMessages: [
          {
            id: "msg-voice-locked",
            content: "Original voice transcript.",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "voice_message",
            privateMessageHint: "A voice message is waiting.",
          },
        ],
        unlockMessageOutput: {
          messageId: "msg-voice-locked",
          response: makeUnlockPrivateResponse({
            content: "This response content must be ignored.",
            audioUrl: "https://example.com/unlocked-voice.mp3",
          }),
        },
      }),
    ).start();

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

    actor.send({
      type: "ChatUnlockMessageRequested",
      messageId: "msg-voice-locked",
      kind: "voice",
    });

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

    expect(actor.getSnapshot().context.messages).toMatchObject([
      {
        id: "msg-voice-locked",
        content: "Original voice transcript.",
        audioUrl: "https://example.com/unlocked-voice.mp3",
        locked: false,
        lockReason: null,
        privateMessageHint: null,
      },
    ]);

    actor.stop();
  });

  it("requests payment when single message unlock lacks credits", async () => {
    const actor = createActor(
      createTestChatMachine({
        historyMessages: [
          {
            id: "msg-voice-locked",
            content: "",
            isFromAI: true,
            date: "2026-06-29",
            locked: true,
            lockReason: "voice_message",
            privateMessageHint: "A voice message is waiting.",
          },
        ],
        unlockMessageOutput: {
          messageId: "msg-voice-locked",
          response: makeUnlockPrivateResponse({
            unlocked: false,
            content: "",
            reason: "insufficient_balance",
            creditBalance: 3,
            creditsCharged: 0,
            requiredCredits: 10,
            shortfallCredits: 7,
          }),
        },
      }),
    ).start();

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

    actor.send({
      type: "ChatUnlockMessageRequested",
      messageId: "msg-voice-locked",
      kind: "voice",
    });

    await waitFor(
      actor,
      (snapshot) => snapshot.context.unlockPaywallRequest !== null,
    );

    expect(actor.getSnapshot().context.unlockPaywallRequest).toEqual({
      displayMessageId: "msg-voice-locked",
      messageId: "msg-voice-locked",
      kind: "voice",
      reason: "insufficient_balance",
      creditBalance: 3,
      requiredCredits: 10,
      shortfallCredits: 7,
    });
    expect(actor.getSnapshot().context.messages).toMatchObject([
      {
        id: "msg-voice-locked",
        locked: true,
        lockReason: "voice_message",
      },
    ]);
    expect(actor.getSnapshot().context.messages[0]?.audioUrl).toBeUndefined();
    expect(actor.getSnapshot().context.isUnlockingMessage).toBe(false);

    actor.stop();
  });

  it("clears the insufficient credits prompt after payment succeeds", async () => {
    const actor = createActor(createTestChatMachine()).start();

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

    actor.send({
      type: "ChatQueuedHttpDone",
      output: {
        response: ChatSendResponse.from({
          reply: "",
          audioUrl: "",
          messageId: "",
          isGuest: false,
          timestamp: Date.now(),
          image: { type: null, url: null },
          lockDetail: {
            locked: false,
            reason: null,
          },
          canSendMessage: false,
          creditBalance: 0,
          requiredCredits: 2,
          shortfallCredits: 2,
        }),
        reply: null,
      },
    });

    expect(actor.getSnapshot().context.upgradePromptVisible).toBe(true);
    expect(actor.getSnapshot().context.upgradeReason).toBe(
      "insufficient_credits",
    );
    expect(actor.getSnapshot().context.canSendMessage).toBe(false);

    actor.send({ type: "ChatPaymentSucceeded" });

    expect(actor.getSnapshot().context.upgradePromptVisible).toBe(false);
    expect(actor.getSnapshot().context.upgradeReason).toBeNull();
    expect(actor.getSnapshot().context.canSendMessage).toBe(true);

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