import { fromCallback, fromPromise } from "xstate";

import { ExceptionHandler } from "@/core/errors";
import { MessageQueue } from "@/core/net/message-queue";
import type { ChatSendResponse } from "@/data/dto/chat";
import { getChatRepository } from "@/data/repositories/chat_repository";
import { Logger, Result, todayString } from "@/utils";

import type { ChatEvent } from "./chat-events";
import {
  chatAssign,
  type ChatActionArgs,
  type ChatContextArgs,
} from "./chat-flow-actions";
import {
  applyHttpSendOutput,
  beginPendingReply,
  finishPendingReply,
  sendResponseToUiMessage,
} from "./chat-machine.helpers";

const log = new Logger("StoresChatChatSendFlow");

type UiMessage = import("@/data/dto/chat").UiMessage;

export const sendMessageHttpActor = fromPromise<
  { response: ChatSendResponse; reply: UiMessage | null },
  { content: string }
>(async ({ input }) => {
  return sendMessageViaHttp(input.content);
});

export const httpMessageQueueActor = fromCallback<ChatEvent>(
  ({ sendBack, receive }) => {
    return createMessageQueueActor(sendBack, receive);
  },
);

export const appendGuestUserMessageAction = chatAssign(
  ({ context, event }: ChatActionArgs) => {
    if (event.type !== "ChatSendMessage") return {};

    const today = todayString();
    log.debug("[chat-machine] appendGuestUserMessage", {
      contentLength: event.content.length,
      contentPreview: event.content.slice(0, 50),
      quotaMode: "server controlled",
      isReplyingAI: context.isReplyingAI,
    });

    return {
      messages: [
        ...context.messages,
        {
          content: event.content,
          isFromAI: false,
          date: today,
        },
      ],
      upgradePromptVisible: false,
      upgradeReason: null,
    };
  },
);

export const appendUserMessageAction = chatAssign(
  ({ context, event }: ChatActionArgs) => {
    if (event.type !== "ChatSendMessage") return {};

    const today = todayString();
    log.debug("[chat-machine] appendUserMessage", {
      contentLength: event.content.length,
      contentPreview: event.content.slice(0, 50),
      isReplyingAI: context.isReplyingAI,
    });

    return {
      messages: [
        ...context.messages,
        {
          content: event.content,
          isFromAI: false,
          date: today,
        },
      ],
      upgradePromptVisible: false,
      upgradeReason: null,
    };
  },
);

export const appendQueuedSendErrorMessageAction = chatAssign(
  ({ context }: ChatContextArgs) => {
    const messages = [
      ...context.messages,
      {
        content: "Something went wrong. Try sending again?",
        isFromAI: true,
        date: todayString(),
      },
    ];
    return { messages, ...finishPendingReply(context) };
  },
);

export const markQueuedSendStartedAction = chatAssign(
  ({ context }: ChatContextArgs) =>
    beginPendingReply(context),
);

export const applyQueuedHttpOutputAction = chatAssign(
  ({ context, event }: ChatActionArgs) => {
    if (event.type !== "ChatQueuedHttpDone") return {};
    return applyHttpSendOutput(context, event.output);
  },
);

export const clearUpgradePromptAction = chatAssign(() => ({
  upgradePromptVisible: false,
  upgradeReason: null,
  canSendMessage: true,
  cannotSendReason: null,
  requiredCredits: 0,
  shortfallCredits: 0,
}));

function createMessageQueueActor(
  sendBack: (event: ChatEvent) => void,
  receive: (listener: (event: ChatEvent) => void) => void,
): () => void {
  const queue = new MessageQueue();

  queue.setConsumer(async (content) => {
    sendBack({ type: "ChatQueuedSendStarted" });
    try {
      const output = await sendMessageViaHttp(content);
      sendBack({ type: "ChatQueuedHttpDone", output });
    } catch (error) {
      const errorMessage = ExceptionHandler.message(error);
      log.error("[chat-machine] message queue send failed", {
        error,
      });
      sendBack({
        type: "ChatQueuedSendError",
        content,
        errorMessage,
      });
    }
  });

  receive((event) => {
    if (event.type !== "ChatSendMessage") return;
    const content = event.content.trim();
    if (content.length === 0) return;
    queue.enqueue(content);
  });

  return () => queue.dispose();
}

async function sendMessageViaHttp(content: string): Promise<{
  response: ChatSendResponse;
  reply: UiMessage | null;
}> {
  const chatRepo = getChatRepository();
  const result = await chatRepo.sendMessage(content);
  if (Result.isErr(result)) {
    log.error("[chat-machine] sendMessageHttpActor failed", {
      error: result.error,
    });
    throw result.error;
  }
  void chatRepo.prefetchMediaForSendResponse(result.data);
  const isInsufficientCredits =
    result.data.canSendMessage === false &&
    result.data.cannotSendReason === "insufficient_credits" &&
    !hasRenderableSendResponse(result.data);
  return {
    response: result.data,
    reply:
      isInsufficientCredits ? null : sendResponseToUiMessage(result.data),
  };
}

function hasRenderableSendResponse(response: ChatSendResponse): boolean {
  return (
    response.reply.trim().length > 0 ||
    response.audioUrl.trim().length > 0 ||
    Boolean(response.image.url) ||
    response.lockDetail.reason === "private_message" ||
    response.lockDetail.reason === "voice_message" ||
    response.lockDetail.reason === "image_paywall"
  );
}
