import { fromPromise } from "xstate";

import { loadChatRepository } from "@/data/repositories/chat_repository_loader";
import type { PaymentGuidance } from "@/data/schemas/chat";
import { resolveChatConversationKey } from "@/data/repositories/chat_cache_identity";
import { Logger } from "@/utils/logger";
import { Result } from "@/utils/result";
import { isAbortError } from "@/utils/abort";

import { readAndSyncHistory } from "../../chat-history-sync";
import {
  type UnlockMessageRequest,
  type UnlockMessageOutput,
} from "../../helper/unlock";

const log = new Logger("StoresChatChatUnlockFlow");

type UiMessage = import("@/stores/chat/ui-message").UiMessage;

export interface UnlockHistoryOutput {
  unlocked: boolean;
  reason: string;
  shortfallCredits: number;
  messages: UiMessage[];
  paymentGuidance?: PaymentGuidance | null;
}

export const unlockHistoryActor = fromPromise<
  UnlockHistoryOutput,
  { characterId: string; emptyChatGreeting: string }
>(async ({ input, signal }) => {
  const chatRepo = await loadChatRepository();
  const unlockResult = await chatRepo.unlockHistory(input.characterId, {
    signal,
  });
  if (Result.isErr(unlockResult)) {
    if (isAbortError(unlockResult.error)) throw unlockResult.error;
    log.error("[chat-machine] unlockHistoryActor failed", {
      error: unlockResult.error,
    });
    throw unlockResult.error;
  }

  signal.throwIfAborted();
  const history = await readAndSyncHistory(
    input.characterId,
    input.emptyChatGreeting,
    signal,
  );
  return {
    unlocked: unlockResult.data.unlocked,
    reason: unlockResult.data.reason,
    shortfallCredits: unlockResult.data.shortfallCredits,
    messages: history.messages,
    paymentGuidance: unlockResult.data.paymentGuidance,
  };
});

export const unlockMessageActor = fromPromise<
  UnlockMessageOutput,
  UnlockMessageRequest & { characterId: string }
>(async ({ input, signal }) => {
  const chatRepo = await loadChatRepository();
  const cacheIdentityResult = await resolveChatConversationKey(
    input.characterId,
  );
  const unlockResult = await chatRepo.unlockPrivateMessage(
    {
      characterId: input.characterId,
      ...(input.messageId ? { messageId: input.messageId } : {}),
      ...(input.lockType ? { lockType: input.lockType } : {}),
      ...(input.clientLockId ? { clientLockId: input.clientLockId } : {}),
    },
    { signal },
  );
  if (Result.isErr(unlockResult)) {
    if (isAbortError(unlockResult.error)) throw unlockResult.error;
    log.error("[chat-machine] unlockMessageActor failed", {
      messageId: input.messageId,
      clientLockId: input.clientLockId,
      error: unlockResult.error,
    });
    throw unlockResult.error;
  }
  signal.throwIfAborted();

  if (
    unlockResult.data.unlocked &&
    input.messageId &&
    !input.clientLockId &&
    Result.isOk(cacheIdentityResult)
  ) {
    const markResult = await chatRepo.markPrivateMessageUnlockedInLocal(
      input.messageId,
      {
        content: unlockResult.data.content,
        audioUrl: unlockResult.data.audioUrl,
        ...(unlockResult.data.image.url
          ? { image: unlockResult.data.image }
          : {}),
        lockDetail: { locked: false, reason: null },
      },
      cacheIdentityResult.data,
    );
    if (Result.isErr(markResult)) {
      log.warn("[chat-machine] mark unlocked local message failed", {
        messageId: input.messageId,
        error: markResult.error,
      });
    }
  }

  return {
    displayMessageId: input.displayMessageId,
    request: input,
    response: unlockResult.data,
  };
});
