import type { UiMessage } from "@/stores/chat/ui-message";
import { resolveChatConversationKey } from "@/data/repositories/chat_cache_identity";
import { loadChatRepository } from "@/data/repositories/chat_repository_loader";
import { Logger } from "@/utils/logger";
import { Result } from "@/utils/result";
import { todayString } from "@/utils/date";
import { isAbortError } from "@/utils/abort";
import { getCharacterErrorCode } from "@/data/services/api";

import { CHAT_HISTORY_LIMIT } from "./helper/history";
import { localMessagesToUi } from "./helper/message-mappers";

const log = new Logger("StoresChatChatHistorySync");

export type ReadAndSyncHistoryOutput = {
  /** Network-authoritative messages. Empty network history includes a UI greeting. */
  messages: UiMessage[];
  /** Display identities present in the local snapshot before the request. */
  localDisplayIds: readonly string[];
  /** True when network history was written back to local storage. */
  localOverwritten: boolean;
  localCount: number;
  networkCount: number;
  total: number;
  limit: number;
};

export type LocalHistorySnapshotOutput = {
  /** Local cached messages. Empty local history includes a UI greeting. */
  messages: UiMessage[];
  localCount: number;
};

export type NetworkHistorySyncOutput = ReadAndSyncHistoryOutput;

export function createGreetingMessage(
  characterId: string,
  content: string,
): UiMessage {
  return {
    displayId: `greeting:${encodeURIComponent(characterId)}`,
    content,
    isFromAI: true,
    date: todayString(),
    isSynthetic: true,
  };
}

export async function resolveHistoryCacheIdentity(
  characterId: string,
): Promise<string | null> {
  const result = await resolveChatConversationKey(characterId);
  return Result.isOk(result) ? result.data : null;
}

export async function readLocalHistorySnapshot(
  cacheIdentity: string | null,
  characterId: string,
  emptyChatGreeting: string,
): Promise<LocalHistorySnapshotOutput> {
  const chatRepo = await loadChatRepository();
  const greetingMessage = createGreetingMessage(
    characterId,
    emptyChatGreeting,
  );

  const localResult = cacheIdentity
    ? await chatRepo.getLocalMessages(cacheIdentity)
    : null;
  const localMessages =
    localResult && Result.isOk(localResult) && localResult.data
      ? localMessagesToUi(localResult.data)
      : [];
  log.debug("[chat-machine] loadHistory LOCAL DONE", {
    count: localMessages.length,
  });

  const snapshotMessages =
    localMessages.length === 0 ? [greetingMessage] : localMessages;
  if (snapshotMessages[0] === greetingMessage) {
    log.debug("[chat-machine] loadHistory EMPTY -> prepend greeting (local)");
  }

  return {
    messages: snapshotMessages,
    localCount: localMessages.length,
  };
}

export async function syncNetworkHistory(
  characterId: string,
  localCount: number,
  localDisplayIds: readonly string[],
  cacheIdentity: string | null,
  emptyChatGreeting: string,
  signal?: AbortSignal,
): Promise<NetworkHistorySyncOutput | null> {
  const chatRepo = await loadChatRepository();
  const greetingMessage = createGreetingMessage(
    characterId,
    emptyChatGreeting,
  );

  const openingResult = await chatRepo.saveOpeningMessage(
    characterId,
    emptyChatGreeting,
    { signal },
  );
  if (Result.isErr(openingResult)) {
    if (isAbortError(openingResult.error)) throw openingResult.error;
    log.warn("[chat-machine] opening message persistence skipped", {
      characterId,
      error: openingResult.error,
    });
  }
  signal?.throwIfAborted();

  const networkResult = await chatRepo.getHistory(
    characterId,
    CHAT_HISTORY_LIMIT,
    0,
    { signal },
  );
  if (Result.isErr(networkResult)) {
    if (isAbortError(networkResult.error)) throw networkResult.error;
    if (getCharacterErrorCode(networkResult.error)) throw networkResult.error;
    log.error("[chat-machine] loadHistory NETWORK FAILED", {
      error: networkResult.error,
    });
    return null;
  }
  signal?.throwIfAborted();

  const networkUi = localMessagesToUi(networkResult.data.messages);
  log.debug("[chat-machine] loadHistory NETWORK DONE", {
    count: networkUi.length,
  });
  if (cacheIdentity) {
    void chatRepo.prefetchMediaForMessages(
      networkResult.data.messages,
      characterId,
      cacheIdentity,
    );
  }

  const saveResult = cacheIdentity
    ? await chatRepo.saveMessagesToLocal(
        networkResult.data.messages,
        cacheIdentity,
      )
    : null;
  const localOverwritten = saveResult !== null && Result.isOk(saveResult);
  log.debug("[chat-machine] loadHistory SAVE TO LOCAL DONE", {
    localOverwritten,
  });

  const finalMessages =
    networkUi.length === 0 ? [greetingMessage] : networkUi;
  if (finalMessages[0] === greetingMessage) {
    log.debug("[chat-machine] loadHistory EMPTY -> prepend greeting");
  }

  return {
    messages: finalMessages,
    localDisplayIds,
    localOverwritten,
    localCount,
    networkCount: networkUi.length,
    total: networkResult.data.total,
    limit: networkResult.data.limit,
  };
}

/**
 * History sync flow:
 * 1. Read local history for fallback.
 * 2. Read network history as the authoritative source.
 * 3. Overwrite local history with network data.
 */
export async function readAndSyncHistory(
  characterId: string,
  emptyChatGreeting: string,
  signal?: AbortSignal,
): Promise<ReadAndSyncHistoryOutput> {
  const cacheIdentity = await resolveHistoryCacheIdentity(characterId);
  const localSnapshot = await readLocalHistorySnapshot(
    cacheIdentity,
    characterId,
    emptyChatGreeting,
  );
  const networkSnapshot = await syncNetworkHistory(
    characterId,
    localSnapshot.localCount,
    localSnapshot.messages.map((message) => message.displayId),
    cacheIdentity,
    emptyChatGreeting,
    signal,
  );
  if (networkSnapshot) return networkSnapshot;

  return {
    messages: localSnapshot.messages,
    localDisplayIds: localSnapshot.messages.map(
      (message) => message.displayId,
    ),
    localOverwritten: false,
    localCount: localSnapshot.localCount,
    networkCount: 0,
    total: 0,
    limit: CHAT_HISTORY_LIMIT,
  };
}
