/**
 * Asset Cache Service
 *
 * Unified service for asset caching across online preload and offline download modes.
 * This service provides a single entry point for:
 * - Discovering assets from pages/elements/links
 * - Checking cache status (fully cached, partially cached, or missing)
 * - Queueing downloads with appropriate parameters
 * - Managing storage keys consistently
 */

import { StorageManager } from '../offline/StorageManager';
import { downloadManager } from '../offline/DownloadManager';
import {
  discoverProjectAssets,
  getPrioritizedAssets,
  type AssetToCache,
  type AssetDiscoveryOptions,
} from './assetDiscovery';
import {
  queuePresignedUrls,
  resolveAssetPlaybackUrl,
  isPresignedUrl,
  markPresignedUrlFailed,
  markPresignedUrlsVerified,
  buildProxyUrl,
  isRelativeStoragePath,
} from '../assetUrl';
import { PRELOAD_CONFIG } from '../../config/preload.config';
import { logger } from '../logger';
import type {
  PreloadPage,
  PreloadPageLink,
  PreloadElement,
} from '../../types/preload';
import type { AssetType, AssetVariantType, CachedAssetInfo } from '../../types/offline';

// Re-export for convenience
export type { CachedAssetInfo };

/**
 * Download progress callback
 */
export interface DownloadProgress {
  totalAssets: number;
  downloadedAssets: number;
  totalBytes: number;
  downloadedBytes: number;
  progress: number;
}

/**
 * Options for queueing downloads
 */
export interface QueueDownloadOptions {
  mode: 'online' | 'offline';
  projectId?: string;
  presignedUrls?: Record<string, string>;
  onProgress?: (progress: DownloadProgress) => void;
  currentPageId?: string; // For online mode: current page assets get full downloads
}

/**
 * AssetCacheService - Unified entry point for asset caching
 */
export class AssetCacheService {
  /**
   * Discover all assets for a project from page data
   * Single source of truth for both online and offline
   */
  static discoverProjectAssets(
    pages: PreloadPage[],
    pageLinks: PreloadPageLink[],
    elements: PreloadElement[],
    options?: AssetDiscoveryOptions,
  ): AssetToCache[] {
    return discoverProjectAssets(pages, pageLinks, elements, options);
  }

  /**
   * Get prioritized assets for preloading based on current page
   */
  static getPrioritizedAssets(
    currentPageId: string,
    pages: PreloadPage[],
    pageLinks: PreloadPageLink[],
    elements: PreloadElement[],
    neighborPageIds: Array<{ pageId: string; distance: number }>,
    options?: AssetDiscoveryOptions,
  ): AssetToCache[] {
    return getPrioritizedAssets(
      currentPageId,
      pages,
      pageLinks,
      elements,
      neighborPageIds,
      options,
    );
  }

  /**
   * Get cache status for a single asset
   * Returns whether it exists, if it's partial, and size info
   */
  static async getAssetInfo(storageKey: string): Promise<CachedAssetInfo | null> {
    return StorageManager.getAssetInfo(storageKey);
  }

  /**
   * Get cache status for multiple assets efficiently
   * Returns a map of storageKey -> CachedAssetInfo
   */
  static async checkCacheStatus(
    storageKeys: string[],
  ): Promise<Map<string, CachedAssetInfo>> {
    const results = new Map<string, CachedAssetInfo>();

    // Process in parallel with a concurrency limit
    const BATCH_SIZE = 20;
    for (let i = 0; i < storageKeys.length; i += BATCH_SIZE) {
      const batch = storageKeys.slice(i, i + BATCH_SIZE);
      const batchResults = await Promise.all(
        batch.map(async (storageKey) => {
          const info = await StorageManager.getAssetInfo(storageKey);
          return { storageKey, info };
        }),
      );

      batchResults.forEach(({ storageKey, info }) => {
        if (info) {
          results.set(storageKey, info);
        } else {
          results.set(storageKey, {
            storageKey,
            exists: false,
            isPartial: false,
            sizeBytes: 0,
          });
        }
      });
    }

    return results;
  }

  /**
   * Filter assets to only those needing download
   * - Not cached at all -> needs download
   * - Partially cached + offline mode -> needs full download
   * - Fully cached -> skip
   */
  static async filterAssetsNeedingDownload(
    assets: AssetToCache[],
    mode: 'online' | 'offline',
  ): Promise<AssetToCache[]> {
    const storageKeys = assets.map((a) => a.storageKey);
    const cacheStatus = await this.checkCacheStatus(storageKeys);

    return assets.filter((asset) => {
      const status = cacheStatus.get(asset.storageKey);
      if (!status) return true; // Not in cache, needs download

      if (!status.exists) return true; // Not cached, needs download

      // In offline mode, partial downloads need to be fully downloaded
      if (mode === 'offline' && status.isPartial) return true;

      // Fully cached, skip
      return false;
    });
  }

  /**
   * Resolve download URL for an asset
   * Prefers presigned URL if available, falls back to proxy
   */
  static resolveDownloadUrl(
    storageKey: string,
    presignedUrls?: Record<string, string>,
  ): string {
    // Check presigned URL cache first
    if (presignedUrls && presignedUrls[storageKey]) {
      return presignedUrls[storageKey];
    }

    // Use resolveAssetPlaybackUrl for fallback
    return resolveAssetPlaybackUrl(storageKey);
  }

  /**
   * Queue assets for download with unified parameters
   * Handles both online (partial) and offline (full) modes
   */
  static async queueDownloads(
    assets: AssetToCache[],
    options: QueueDownloadOptions,
  ): Promise<void> {
    const { mode, projectId = '', presignedUrls = {}, currentPageId } = options;

    // Fetch presigned URLs for assets that need them
    const storagePaths = assets
      .filter((a) => isRelativeStoragePath(a.originalUrl))
      .map((a) => a.storageKey);

    let resolvedPresignedUrls = { ...presignedUrls };
    if (storagePaths.length > 0 && mode === 'online') {
      try {
        const fetchedUrls = await queuePresignedUrls(storagePaths);
        resolvedPresignedUrls = { ...resolvedPresignedUrls, ...fetchedUrls };
      } catch (error) {
        logger.error('[AssetCacheService] Failed to fetch presigned URLs', {
          error: error instanceof Error ? error.message : 'unknown',
        });
      }
    }

    // Queue each asset for download
    for (const asset of assets) {
      const downloadUrl = this.resolveDownloadUrl(
        asset.storageKey,
        resolvedPresignedUrls,
      );

      if (!downloadUrl) continue;

      // Determine download parameters based on mode
      const isOnlineMode = mode === 'online';
      const isCurrentPageAsset = currentPageId && asset.pageId === currentPageId;

      // Online mode: use partial downloads for neighbor page media
      // Offline mode: always full downloads
      const maxBytes = this.getMaxBytesForAsset(
        asset.assetType,
        isOnlineMode,
        !isCurrentPageAsset,
      );

      // Create blob URL for images (instant navigation) and full downloads
      const createBlobUrl =
        asset.assetType === 'image' || maxBytes === undefined;

      try {
        await downloadManager
          .addJob({
            assetId: `${mode}-${asset.storageKey}`,
            projectId,
            url: downloadUrl,
            filename: asset.storageKey.split('/').pop() || 'asset',
            variantType: 'original' as AssetVariantType,
            assetType: asset.assetType,
            priority: asset.priority,
            storageKey: asset.storageKey,
            createBlobUrl,
            persist: mode === 'offline', // Only persist for offline mode
            maxBytes,
          })
          .then(() => {
            if (isPresignedUrl(downloadUrl)) {
              markPresignedUrlsVerified();
            }
          })
          .catch(async (err) => {
            logger.error('[AssetCacheService] Download failed', {
              storageKey: asset.storageKey.slice(-50),
              error: err?.message,
            });

            // Retry with proxy if presigned URL failed
            if (isPresignedUrl(downloadUrl)) {
              markPresignedUrlFailed(asset.storageKey);
              const proxyUrl = buildProxyUrl(asset.storageKey);

              try {
                await downloadManager.addJob({
                  assetId: `${mode}-retry-${asset.storageKey}`,
                  projectId,
                  url: proxyUrl,
                  filename: asset.storageKey.split('/').pop() || 'asset',
                  variantType: 'original' as AssetVariantType,
                  assetType: asset.assetType,
                  priority: asset.priority,
                  storageKey: asset.storageKey,
                  createBlobUrl,
                  persist: mode === 'offline',
                  maxBytes,
                });
              } catch {
                // Ignore retry failures
              }
            }
          });
      } catch (error) {
        logger.error('[AssetCacheService] Failed to queue download', {
          storageKey: asset.storageKey.slice(-50),
          error: error instanceof Error ? error.message : 'unknown',
        });
      }
    }

    logger.info('[AssetCacheService] Downloads queued', {
      mode,
      count: assets.length,
      projectId: projectId || 'none',
    });
  }

  /**
   * Determine max bytes for partial preload (online mode only)
   */
  private static getMaxBytesForAsset(
    assetType: AssetType,
    isOnlineMode: boolean,
    isNeighborPage: boolean,
  ): number | undefined {
    // Offline mode: always full downloads
    if (!isOnlineMode) return undefined;

    // Partial preload disabled
    if (!PRELOAD_CONFIG.partialPreload.enabled) return undefined;

    // Transitions always use partial preload
    if (assetType === 'transition') {
      return PRELOAD_CONFIG.partialPreload.transitionMaxBytes;
    }

    // Current page assets should be fully downloaded
    if (!isNeighborPage) return undefined;

    // Neighbor page media uses partial preload
    switch (assetType) {
      case 'video':
        return PRELOAD_CONFIG.partialPreload.videoMaxBytes;
      case 'audio':
        return PRELOAD_CONFIG.partialPreload.audioMaxBytes;
      default:
        return undefined; // Images need full download for display
    }
  }

  /**
   * Clear project cache
   */
  static async clearProjectCache(projectId: string): Promise<void> {
    await StorageManager.deleteProjectAssets(projectId);
    logger.info('[AssetCacheService] Project cache cleared', { projectId });
  }

  /**
   * Get ready blob URL for instant display (O(1) lookup)
   */
  static getReadyBlobUrl(url: string): string | null {
    return downloadManager.getReadyBlobUrl(url);
  }

  /**
   * Check if asset is preloaded (in memory blob URL cache)
   */
  static hasReadyBlobUrl(url: string): boolean {
    return downloadManager.getReadyBlobUrl(url) !== null;
  }
}

export default AssetCacheService;
