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

import { ChatSendResponse } from "@/data/dto/chat";
import type { ChatState } from "@/stores/chat/chat-state";
import {
  applyHttpSendOutput,
  countLockedHistoryMessages,
  localMessagesToUi,
  sendResponseToUiMessage,
} from "@/stores/chat/helper";

function makeResponse(
  overrides: Partial<Parameters<typeof ChatSendResponse.from>[0]> = {},
): ChatSendResponse {
  return ChatSendResponse.from({
    reply: "AI reply",
    messageId: "msg-1",
    isGuest: false,
    timestamp: 1782356425363,
    image: { type: null, url: null },
    lockDetail: {
      locked: false,
      reason: null,
    },
    ...overrides,
  });
}

function makeChatState(overrides: Partial<ChatState> = {}): ChatState {
  return {
    messages: [],
    promotion: null,
    isReplyingAI: true,
    pendingReplyCount: 1,
    upgradePromptVisible: false,
    upgradeReason: null,
    canSendMessage: true,
    creditBalance: 0,
    creditsCharged: 0,
    requiredCredits: 0,
    shortfallCredits: 0,
    historyLoaded: true,
    paymentUnlockPending: false,
    unlockHistoryPromptVisible: false,
    lockedHistoryCount: 0,
    isUnlockingHistory: false,
    unlockHistoryError: null,
    isUnlockingMessage: false,
    unlockingMessageId: null,
    unlockingMessageKind: null,
    unlockingRemoteMessageId: null,
    unlockingLockType: null,
    unlockingClientLockId: null,
    unlockMessageError: null,
    unlockPaywallRequest: null,
    ...overrides,
  };
}

describe("sendResponseToUiMessage", () => {
  it("maps locked voice messages without treating them as private text", () => {
    const message = sendResponseToUiMessage(
      makeResponse({
        lockDetail: {
          locked: true,
          reason: "voice_message",
        },
      }),
    );

    expect(message.content).toBe("AI reply");
    expect(message.audioUrl).toBeUndefined();
    expect(message.locked).toBe(true);
    expect(message.lockReason).toBe("voice_message");
    expect(message.lockedPrivate).toBeUndefined();
    expect(message.privateMessageHint).toBeUndefined();
  });

  it("does not expose an audio URL from a locked send response", () => {
    const message = sendResponseToUiMessage(
      makeResponse({
        audioUrl: "https://example.com/locked-voice.mp3",
        lockDetail: {
          locked: true,
          reason: "voice_message",
        },
      }),
    );

    expect(message.audioUrl).toBeUndefined();
    expect(message.locked).toBe(true);
  });

  it("maps an audio URL from an unlocked send response", () => {
    const message = sendResponseToUiMessage(
      makeResponse({
        audioUrl: "https://example.com/unlocked-voice.mp3",
      }),
    );

    expect(message.audioUrl).toBe(
      "https://example.com/unlocked-voice.mp3",
    );
    expect(message.locked).toBe(false);
  });

  it("maps locked private text messages as private messages", () => {
    const message = sendResponseToUiMessage(
      makeResponse({
        lockDetail: {
          locked: true,
          reason: "private_message",
        },
      }),
    );

    expect(message.content).toBe("AI reply");
    expect(message.locked).toBe(true);
    expect(message.lockReason).toBe("private_message");
    expect(message.isPrivate).toBe(true);
    expect(message.lockedPrivate).toBe(true);
    expect(message.privateMessageHint).toBeUndefined();
  });

  it("keeps paywalled image messages visible but locked", () => {
    const message = sendResponseToUiMessage(
      makeResponse({
        image: {
          type: "jpg",
          url: "https://example.com/private-photo.jpg",
        },
        lockDetail: {
          locked: true,
          reason: "image",
        },
      }),
    );

    expect(message.content).toBe("");
    expect(message.imageUrl).toBe("https://example.com/private-photo.jpg");
    expect(message.imagePaywalled).toBe(true);
    expect(message.locked).toBe(true);
    expect(message.lockReason).toBe("image");
    expect(message.lockedPrivate).toBeUndefined();
  });

  it("does not mark a message as paywalled when its lock reason is unlocked", () => {
    const message = sendResponseToUiMessage(
      makeResponse({
        image: {
          type: "jpg",
          url: "https://example.com/unlocked-photo.jpg",
        },
        lockDetail: {
          locked: false,
          reason: "image_paywall",
        },
      }),
    );

    expect(message.locked).toBe(false);
    expect(message.lockReason).toBe("image_paywall");
    expect(message.imagePaywalled).toBeUndefined();
  });
});

describe("applyHttpSendOutput", () => {
  it("stores insufficient credit state and removes failed optimistic messages", () => {
    const context = makeChatState({
      messages: [
        {
          content: "hello",
          isFromAI: false,
          date: "2026-06-25",
        },
      ],
    });

    const nextState = applyHttpSendOutput(context, {
      response: makeResponse({
        reply: "",
        messageId: "",
        canSendMessage: false,
        creditBalance: 0,
        creditsCharged: 0,
        requiredCredits: 2,
        shortfallCredits: 2,
        lockDetail: {
          locked: false,
          reason: null,
        },
      }),
      reply: null,
    });

    expect(nextState.messages).toEqual([]);
    expect(nextState.isReplyingAI).toBe(false);
    expect(nextState.upgradePromptVisible).toBe(true);
    expect(nextState.upgradeReason).toBe("insufficient_credits");
    expect(nextState.canSendMessage).toBe(false);
    expect(nextState.requiredCredits).toBe(2);
    expect(nextState.shortfallCredits).toBe(2);
  });

  it("keeps a valid reply while marking future sends as unavailable", () => {
    const context = makeChatState({
      messages: [
        {
          content: "hello",
          isFromAI: false,
          date: "2026-06-25",
        },
      ],
    });
    const reply = sendResponseToUiMessage(
      makeResponse({
        reply: "This one went through, but you need credits next.",
        canSendMessage: false,
        creditBalance: 0,
        requiredCredits: 2,
        shortfallCredits: 2,
      }),
    );

    const nextState = applyHttpSendOutput(context, {
      response: makeResponse({
        reply: "This one went through, but you need credits next.",
        canSendMessage: false,
        creditBalance: 0,
        requiredCredits: 2,
        shortfallCredits: 2,
      }),
      reply,
    });

    expect(nextState.messages).toHaveLength(2);
    expect(nextState.messages?.[1]).toMatchObject({
      content: "This one went through, but you need credits next.",
      isFromAI: true,
    });
    expect(nextState.upgradePromptVisible).toBe(true);
    expect(nextState.upgradeReason).toBe("insufficient_credits");
    expect(nextState.canSendMessage).toBe(false);
  });
});

describe("localMessagesToUi", () => {
  it("maps locked voice messages from history", () => {
    const [message] = localMessagesToUi([
      {
        id: "voice-1",
        role: "assistant",
        type: "voice",
        content: "hidden voice transcript",
        createdAt: "2026-06-25T12:00:00.000Z",
        audioUrl: "https://example.com/locked-history-voice.mp3",
        image: { type: null, url: null },
        lockDetail: {
          locked: true,
          reason: "voice_message",
        },
      },
    ]);

    expect(message.content).toBe("hidden voice transcript");
    expect(message.audioUrl).toBeUndefined();
    expect(message.locked).toBe(true);
    expect(message.lockReason).toBe("voice_message");
    expect(message.lockedPrivate).toBeUndefined();
    expect(message.privateMessageHint).toBeUndefined();
  });

  it("hides text content for image history messages", () => {
    const [message] = localMessagesToUi([
      {
        id: "image-1",
        role: "assistant",
        type: "image",
        content: "This text should not render with an image.",
        createdAt: "2026-06-25T12:00:00.000Z",
        audioUrl: null,
        image: {
          type: "jpg",
          url: "https://example.com/image.jpg",
        },
        lockDetail: {
          locked: false,
          reason: null,
        },
      },
    ]);

    expect(message.content).toBe("");
    expect(message.imageUrl).toBe("https://example.com/image.jpg");
    expect(message.imagePaywalled).toBeUndefined();
  });
});

describe("countLockedHistoryMessages", () => {
  it("counts only unlockable locked AI messages", () => {
    expect(
      countLockedHistoryMessages([
        {
          content: "",
          isFromAI: true,
          date: "2026-06-29",
          locked: true,
          lockReason: "private_message",
        },
        {
          content: "",
          isFromAI: true,
          date: "2026-06-29",
          locked: true,
          lockReason: "voice_message",
        },
        {
          content: "",
          isFromAI: true,
          date: "2026-06-29",
          locked: true,
          imageUrl: "https://example.com/locked.jpg",
          imagePaywalled: true,
          lockReason: "image",
        },
        {
          content: "",
          isFromAI: true,
          date: "2026-06-29",
          locked: true,
          lockReason: "insufficient_credits",
        },
        {
          content: "user text",
          isFromAI: false,
          date: "2026-06-29",
          locked: true,
          lockReason: "private_message",
        },
      ]),
    ).toBe(3);
  });
});
