import {compressImageBatch, compressVideo} from '@modules/chats/utils';
import {MediaEntity} from '@modules/chats/interfaces';
import {Storage} from '@services/mmkvFactory';

import {fileHandler} from './FileHandler';

export type ImageCacheEntry = {
  type: 'image';
  uri: string;
  height: number;
  width: number;
  size: number;
};

export type VideoCacheEntry = {
  type: 'video';
  compressedUri: string;
  height: number;
  width: number;
  sizeInBytes: number;
  thumbnailUrl: string | null;
};

export type CompressionCacheEntry = ImageCacheEntry | VideoCacheEntry;

export class FileCompressionHandler {
  private static instance: FileCompressionHandler;
  private compressionCache = new Map<string, CompressionCacheEntry>();
  private compressionStorage = new Storage({id: 'compression_cache'});

  private constructor() {
    this.loadCompressionCache();
  }

  public static getInstance(): FileCompressionHandler {
    if (!FileCompressionHandler.instance) {
      FileCompressionHandler.instance = new FileCompressionHandler();
    }
    return FileCompressionHandler.instance;
  }

  private loadCompressionCache(): void {
    try {
      const keys = this.compressionStorage.getAllKeys();
      for (const key of keys) {
        const val = this.compressionStorage.getString(key);
        if (val) {
          const entry = JSON.parse(val) as CompressionCacheEntry;
          this.compressionCache.set(key, entry);
        }
      }
    } catch {
      // Cache will be empty; entries repopulate on next use
    }
  }

  public getCachedEntry(hash: string): CompressionCacheEntry | undefined {
    return this.compressionCache.get(hash);
  }

  /**
   * Compress images in batch, reusing cached results when the compressed file still exists on disk.
   * Items are keyed by content hash so the cache survives remove + re-add cycles.
   */
  public async compressImagesWithCache(
    items: {source: string; name: string; hash?: string}[],
  ): Promise<ImageCacheEntry[]> {
    const results: (ImageCacheEntry | null)[] = new Array(items.length).fill(
      null,
    );
    const uncachedIndices: number[] = [];

    await Promise.all(
      items.map(async (item, i) => {
        if (!item.hash) {
          uncachedIndices.push(i);
          return;
        }
        const entry = this.compressionCache.get(item.hash);
        if (entry?.type === 'image') {
          const exists = await fileHandler.checkIfFileExists(entry.uri);
          if (exists) {
            results[i] = entry;
            return;
          }
          this.compressionCache.delete(item.hash);
          this.compressionStorage.remove(item.hash);
        }
        uncachedIndices.push(i);
      }),
    );

    if (uncachedIndices.length > 0) {
      const compressed = await compressImageBatch(
        uncachedIndices.map(i => ({
          source: items[i].source,
          name: items[i].name,
        })),
      );
      compressed.forEach((c, j) => {
        const originalIndex = uncachedIndices[j];
        const hash = items[originalIndex].hash;
        const entry: ImageCacheEntry = {type: 'image', ...c};
        if (hash) {
          this.compressionCache.set(hash, entry);
          this.compressionStorage.set(hash, JSON.stringify(entry));
        }
        results[originalIndex] = entry;
      });
    }

    return results as ImageCacheEntry[];
  }

  /**
   * Compress a video, reusing a cached compressed file when it still exists on disk.
   */
  public async compressVideoWithCache(
    media: MediaEntity,
    hash: string,
  ): Promise<VideoCacheEntry> {
    const cached = this.compressionCache.get(hash);
    if (cached?.type === 'video') {
      const exists = await fileHandler.checkIfFileExists(cached.compressedUri);
      if (exists) {
        return cached;
      }
      this.compressionCache.delete(hash);
      this.compressionStorage.remove(hash);
    }

    const compressed = await compressVideo(media);
    const entry: VideoCacheEntry = {
      type: 'video',
      compressedUri: compressed.localUri,
      height: compressed.height ?? 0,
      width: compressed.width ?? 0,
      sizeInBytes: compressed.sizeInBytes,
      thumbnailUrl: media.fileAsset?.thumbnailUrl ?? null,
    };
    this.compressionCache.set(hash, entry);
    this.compressionStorage.set(hash, JSON.stringify(entry));
    return entry;
  }

  public clearCache(): void {
    this.compressionCache.clear();
    this.compressionStorage.clearAll();
  }
}

export const fileCompressionHandler = FileCompressionHandler.getInstance();
