"use client";

import type { ChatMessage, ChatSendResponse } from "@/data/dto/chat";
import {
  LocalChatMediaStorage,
  type LocalChatMediaRow,
} from "@/data/storage/chat";
import { AuthStorage } from "@/data/storage/auth";
import { UserStorage } from "@/data/storage/user/user_storage";
import { requestBrowserPersistentStorageOnce } from "@/lib/browser/persistent_storage";
import { Logger, Result } from "@/utils";
import type {
  CacheRemoteChatMediaInput,
  ChatMediaLookupInput,
} from "@/data/repositories/interfaces";

import {
  buildChatMediaCacheKey,
  fallbackChatMediaMimeType,
  getMessageMediaTargets,
  getSendResponseMediaTargets,
  isCacheableRemoteChatMediaUrl,
  uniqueMediaTargets,
} from "./chat_media_cache_helpers";

const log = new Logger("DataRepositoriesChatMediaCacheCoordinator");

export class ChatMediaCacheCoordinator {
  constructor(private readonly mediaStorage: LocalChatMediaStorage) {}

  async getCachedMedia(
    input: ChatMediaLookupInput,
  ): Promise<Result<LocalChatMediaRow | null>> {
    return Result.wrap(async () => {
      const ownerKeys = await this._resolveMediaOwnerKeys();
      for (const ownerKey of ownerKeys) {
        const cacheKey = this._buildMediaCacheKey(ownerKey, input);
        const result = await this.mediaStorage.getMedia(cacheKey);
        if (Result.isErr(result)) throw result.error;
        if (result.data) return result.data;
      }
      return null;
    });
  }

  async cacheRemoteMedia(
    input: CacheRemoteChatMediaInput,
  ): Promise<Result<LocalChatMediaRow>> {
    return Result.wrap(() => this._cacheRemoteMediaOrThrow(input));
  }

  async prefetchMediaForMessages(
    messages: readonly ChatMessage[],
  ): Promise<Result<void>> {
    return this._prefetchMediaTargets(
      messages.flatMap((message) => getMessageMediaTargets(message)),
    );
  }

  async prefetchMediaForSendResponse(
    response: ChatSendResponse,
  ): Promise<Result<void>> {
    return this._prefetchMediaTargets(getSendResponseMediaTargets(response));
  }

  private async _prefetchMediaTargets(
    targets: readonly CacheRemoteChatMediaInput[],
  ): Promise<Result<void>> {
    return Result.wrap(async () => {
      for (const target of uniqueMediaTargets(targets)) {
        try {
          await this._cacheRemoteMediaOrThrow(target);
        } catch (error) {
          log.warn("[chat-media] prefetch failed", {
            messageId: target.messageId,
            kind: target.kind,
            remoteUrl: target.remoteUrl,
            error,
          });
        }
      }
    });
  }

  private async _cacheRemoteMediaOrThrow(
    input: CacheRemoteChatMediaInput,
  ): Promise<LocalChatMediaRow> {
    if (!isCacheableRemoteChatMediaUrl(input.remoteUrl)) {
      throw new Error(`Unsupported media url: ${input.remoteUrl}`);
    }

    const ownerKeys = await this._resolveMediaOwnerKeys();
    const ownerKey = ownerKeys[0] ?? "anonymous";
    const cacheKey = this._buildMediaCacheKey(ownerKey, input);
    for (const lookupOwnerKey of ownerKeys) {
      const lookupCacheKey = this._buildMediaCacheKey(lookupOwnerKey, input);
      const existingResult = await this.mediaStorage.getMedia(lookupCacheKey);
      if (Result.isErr(existingResult)) throw existingResult.error;
      if (existingResult.data) return existingResult.data;
    }

    const response = await fetch(input.remoteUrl, {
      method: "GET",
      cache: "force-cache",
    });
    if (!response.ok) {
      throw new Error(
        `Media download failed: ${response.status} ${response.statusText}`,
      );
    }

    const bytes = await response.arrayBuffer();
    if (bytes.byteLength <= 0) {
      throw new Error("Media download returned empty bytes.");
    }
    const mimeType =
      response.headers.get("content-type") ||
      fallbackChatMediaMimeType(input.kind);

    const saveResult = await this.mediaStorage.saveMedia({
      cacheKey,
      ownerKey,
      messageId: input.messageId,
      kind: input.kind,
      remoteUrl: input.remoteUrl,
      bytes,
      mimeType,
    });
    if (Result.isErr(saveResult)) throw saveResult.error;
    void requestBrowserPersistentStorageOnce();

    const savedResult = await this.mediaStorage.getMedia(cacheKey);
    if (Result.isErr(savedResult)) throw savedResult.error;
    if (!savedResult.data) {
      throw new Error("Cached media could not be read after save.");
    }
    return savedResult.data;
  }

  private async _resolveMediaOwnerKeys(): Promise<string[]> {
    const ownerKeys: string[] = [];
    const userIdResult = await UserStorage.getInstance().getUserId();
    if (
      Result.isOk(userIdResult) &&
      userIdResult.data !== null &&
      userIdResult.data.length > 0
    ) {
      ownerKeys.push(`user:${userIdResult.data}`);
    }

    const deviceIdResult = await AuthStorage.getInstance().getDeviceId();
    if (
      Result.isOk(deviceIdResult) &&
      deviceIdResult.data !== null &&
      deviceIdResult.data.length > 0
    ) {
      ownerKeys.push(`device:${deviceIdResult.data}`);
    }

    ownerKeys.push("anonymous");
    return Array.from(new Set(ownerKeys));
  }

  private _buildMediaCacheKey(
    ownerKey: string,
    input: ChatMediaLookupInput,
  ): string {
    return buildChatMediaCacheKey({
      ownerKey,
      messageId: input.messageId,
      kind: input.kind,
    });
  }
}
