/**
 * StorageManager
 *
 * Abstraction layer for storing assets in Cache API or IndexedDB.
 * Small files go to Cache API, large files (videos > 5MB) go to IndexedDB.
 */

import { OFFLINE_CONFIG } from '../../config/offline.config';
import { PRELOAD_CONFIG } from '../../config/preload.config';
import { OfflineDbManager } from '../offlineDb/OfflineDbManager';
import type {
  OfflineAsset,
  AssetVariantType,
  AssetType,
  StorageQuotaInfo,
} from '../../types/offline';

export class StorageManager {
  /**
   * Get storage quota information
   */
  static async getStorageQuota(): Promise<StorageQuotaInfo> {
    if (typeof navigator === 'undefined' || !navigator.storage?.estimate) {
      return {
        usage: 0,
        quota: Infinity,
        percentUsed: 0,
        available: Infinity,
        canStore: () => true,
      };
    }

    try {
      const { usage = 0, quota = Infinity } =
        await navigator.storage.estimate();
      const percentUsed = quota > 0 ? (usage / quota) * 100 : 0;
      const available = quota - usage;

      return {
        usage,
        quota,
        percentUsed,
        available,
        canStore: (bytes: number) =>
          available - bytes > PRELOAD_CONFIG.storage.minFreeBuffer,
      };
    } catch {
      return {
        usage: 0,
        quota: Infinity,
        percentUsed: 0,
        available: Infinity,
        canStore: () => true,
      };
    }
  }

  /**
   * Request persistent storage (prevents browser from clearing data)
   */
  static async requestPersistentStorage(): Promise<boolean> {
    if (typeof navigator === 'undefined' || !navigator.storage?.persist) {
      return false;
    }

    try {
      // Check if already persisted
      const isPersisted = await navigator.storage.persisted();
      if (isPersisted) return true;

      // Request persistence
      return await navigator.storage.persist();
    } catch {
      return false;
    }
  }

  /**
   * Determine if an asset should be stored in IndexedDB (large files)
   * or Cache API (small files)
   */
  static shouldUseIndexedDB(sizeBytes: number): boolean {
    return sizeBytes >= OFFLINE_CONFIG.storage.indexedDbMinSize;
  }

  /**
   * Store an asset (auto-selects Cache API or IndexedDB)
   */
  static async storeAsset(
    url: string,
    blob: Blob,
    metadata: {
      id: string;
      projectId: string;
      filename: string;
      variantType: AssetVariantType;
      assetType: AssetType;
    },
  ): Promise<void> {
    const sizeBytes = blob.size;

    if (this.shouldUseIndexedDB(sizeBytes)) {
      // Store in IndexedDB for large files
      const asset: OfflineAsset = {
        id: metadata.id,
        projectId: metadata.projectId,
        url,
        filename: metadata.filename,
        variantType: metadata.variantType,
        assetType: metadata.assetType,
        mimeType: blob.type,
        sizeBytes,
        blob,
        downloadedAt: Date.now(),
      };
      await OfflineDbManager.storeAsset(asset);
    } else {
      // Store in Cache API for small files
      const cache = await caches.open(OFFLINE_CONFIG.cacheNames.assets);
      const response = new Response(blob, {
        headers: {
          'Content-Type': blob.type,
          'Content-Length': String(sizeBytes),
          'X-Asset-Id': metadata.id,
          'X-Project-Id': metadata.projectId,
        },
      });
      await cache.put(url, response);
    }
  }

  /**
   * Get an asset (checks both Cache API and IndexedDB)
   */
  static async getAsset(url: string): Promise<Blob | null> {
    // Check IndexedDB first (for large files)
    const indexedAsset = await OfflineDbManager.getAssetByUrl(url);
    if (indexedAsset) {
      return indexedAsset.blob;
    }

    // Check Cache API
    if (typeof caches !== 'undefined') {
      const cache = await caches.open(OFFLINE_CONFIG.cacheNames.assets);
      const response = await cache.match(url);
      if (response) {
        return response.blob();
      }
    }

    return null;
  }

  /**
   * Check if an asset exists
   */
  static async hasAsset(url: string): Promise<boolean> {
    // Check IndexedDB
    const hasInDb = await OfflineDbManager.hasAssetByUrl(url);
    if (hasInDb) return true;

    // Check Cache API
    if (typeof caches !== 'undefined') {
      const cache = await caches.open(OFFLINE_CONFIG.cacheNames.assets);
      const response = await cache.match(url);
      if (response) return true;
    }

    return false;
  }

  /**
   * Delete an asset from all storage locations
   */
  static async deleteAsset(url: string, assetId?: string): Promise<void> {
    // Delete from IndexedDB
    if (assetId) {
      await OfflineDbManager.deleteAsset(assetId);
    }

    // Delete from Cache API
    if (typeof caches !== 'undefined') {
      const cache = await caches.open(OFFLINE_CONFIG.cacheNames.assets);
      await cache.delete(url);
    }
  }

  /**
   * Delete all assets for a project
   */
  static async deleteProjectAssets(projectId: string): Promise<void> {
    // Delete from IndexedDB
    await OfflineDbManager.deleteProjectAssets(projectId);

    // Cache API cleanup is more complex - we'd need to track URLs
    // For now, we rely on the service worker to handle this
  }

  /**
   * Get total storage used
   */
  static async getTotalStorageUsed(): Promise<number> {
    let total = 0;

    // IndexedDB storage
    total += await OfflineDbManager.getTotalAssetsSize();

    // Cache API storage (approximate from quota)
    const quota = await this.getStorageQuota();
    // Note: quota.usage includes all storage, not just our caches

    return total;
  }

  /**
   * Clear all offline storage
   */
  static async clearAll(): Promise<void> {
    // Clear IndexedDB
    await OfflineDbManager.clearAll();

    // Clear Cache API
    if (typeof caches !== 'undefined') {
      await caches.delete(OFFLINE_CONFIG.cacheNames.assets);
      await caches.delete(OFFLINE_CONFIG.cacheNames.dynamic);
    }
  }

  /**
   * Send assets to service worker cache
   */
  static async cacheViaServiceWorker(urls: string[]): Promise<void> {
    if (
      typeof navigator === 'undefined' ||
      !navigator.serviceWorker?.controller
    ) {
      return;
    }

    navigator.serviceWorker.controller.postMessage({
      type: 'CACHE_ASSETS',
      payload: { urls },
    });
  }

  /**
   * Check service worker cache status
   */
  static async getServiceWorkerCacheStatus(): Promise<{
    cachedCount: number;
    urls: string[];
  }> {
    return new Promise((resolve) => {
      if (
        typeof navigator === 'undefined' ||
        !navigator.serviceWorker?.controller
      ) {
        resolve({ cachedCount: 0, urls: [] });
        return;
      }

      const handleMessage = (event: MessageEvent) => {
        if (event.data?.type === 'CACHE_STATUS') {
          navigator.serviceWorker.removeEventListener('message', handleMessage);
          resolve(event.data.payload);
        }
      };

      navigator.serviceWorker.addEventListener('message', handleMessage);
      navigator.serviceWorker.controller.postMessage({
        type: 'GET_CACHE_STATUS',
      });

      // Timeout after 5 seconds
      setTimeout(() => {
        navigator.serviceWorker.removeEventListener('message', handleMessage);
        resolve({ cachedCount: 0, urls: [] });
      }, 5000);
    });
  }
}
