/**
 * Audio Storage Module
 *
 * Unified storage manager for audio files supporting:
 * - Look-ahead cache (auto-managed, ephemeral, stored in cache directory)
 * - Future: Full music downloads (user-initiated, permanent, stored in documents)
 *
 * getLocalPath() checks permanent storage first, then cache.
 */

import { EventEmitter } from "eventemitter3";
import { Directory, File, Paths } from "expo-file-system";
import type { EventSubscription } from "expo-modules-core";
import type {
  DownloadCompleteEvent as BGDownloadCompleteEvent,
  DownloadErrorEvent as BGDownloadErrorEvent,
} from "@/modules";
import { BackgroundDownloader } from "@/modules";
import { storage } from "@/utils/mmkv";
import type {
  AudioStorageIndex,
  DownloadCompleteEvent,
  DownloadErrorEvent,
  DownloadOptions,
  StoredTrackInfo,
} from "./types";

// Storage keys
const AUDIO_STORAGE_INDEX_KEY = "audio_storage.v1.json";

// Directory names
const AUDIO_CACHE_DIR = "streamyfin-audio-cache";
const AUDIO_PERMANENT_DIR = "streamyfin-audio";

// Default limits
const DEFAULT_MAX_CACHE_TRACKS = 10;
const DEFAULT_MAX_CACHE_SIZE_BYTES = 500 * 1024 * 1024; // 500MB

// Configurable limits (can be updated at runtime)
let configuredMaxCacheSizeBytes = DEFAULT_MAX_CACHE_SIZE_BYTES;

// Event emitter for notifying about download completion
class AudioStorageEventEmitter extends EventEmitter<{
  complete: (event: DownloadCompleteEvent) => void;
  error: (event: DownloadErrorEvent) => void;
}> {}

export const audioStorageEvents = new AudioStorageEventEmitter();

// Track active downloads: taskId -> { itemId, permanent }
const activeDownloads = new Map<
  number,
  { itemId: string; permanent: boolean }
>();

// Track items being downloaded by itemId for quick lookup
const downloadingItems = new Set<string>();

// Track permanent downloads separately for UI indicator
const permanentDownloadingItems = new Set<string>();

// Cached index (loaded from storage on init)
let storageIndex: AudioStorageIndex | null = null;

// Directories (initialized on first use)
let cacheDir: Directory | null = null;
let permanentDir: Directory | null = null;

// Event listener subscriptions (for cleanup)
let _completeSubscription: EventSubscription | null = null;
let _errorSubscription: EventSubscription | null = null;
let listenersSetup = false;

/**
 * Get the storage index from MMKV
 */
function getStorageIndex(): AudioStorageIndex {
  if (storageIndex) {
    return storageIndex;
  }

  try {
    const data = storage.getString(AUDIO_STORAGE_INDEX_KEY);
    if (data) {
      storageIndex = JSON.parse(data) as AudioStorageIndex;
      return storageIndex;
    }
  } catch {
    // Ignore parse errors
  }

  storageIndex = {
    tracks: {},
    totalCacheSize: 0,
    totalPermanentSize: 0,
  };
  return storageIndex;
}

/**
 * Save the storage index to MMKV
 */
function saveStorageIndex(): void {
  if (storageIndex) {
    try {
      storage.set(AUDIO_STORAGE_INDEX_KEY, JSON.stringify(storageIndex));
    } catch {
      // Ignore save errors
    }
  }
}

/**
 * Ensure directories exist
 */
async function ensureDirectories(): Promise<void> {
  try {
    if (!cacheDir) {
      cacheDir = new Directory(Paths.cache, AUDIO_CACHE_DIR);
      if (!cacheDir.exists) {
        await cacheDir.create();
      }
    }

    if (!permanentDir) {
      permanentDir = new Directory(Paths.document, AUDIO_PERMANENT_DIR);
      if (!permanentDir.exists) {
        await permanentDir.create();
      }
    }
  } catch (error) {
    console.warn("[AudioStorage] Failed to create directories:", error);
  }
}

/**
 * Set the maximum cache size in megabytes
 * Call this when settings change
 */
export function setMaxCacheSizeMB(sizeMB: number): void {
  configuredMaxCacheSizeBytes = sizeMB * 1024 * 1024;
  console.log(
    `[AudioStorage] Max cache size set to ${sizeMB}MB (${configuredMaxCacheSizeBytes} bytes)`,
  );
}

/**
 * Initialize audio storage - call this on app startup
 */
export async function initAudioStorage(): Promise<void> {
  console.log("[AudioStorage] Initializing...");
  try {
    await ensureDirectories();
    getStorageIndex();
    setupEventListeners();
    console.log("[AudioStorage] Initialization complete");
  } catch (error) {
    console.warn("[AudioStorage] Initialization error:", error);
  }
}

/**
 * Set up BackgroundDownloader event listeners
 * Safe to call multiple times - will only set up once
 */
function setupEventListeners(): void {
  // Prevent duplicate listeners
  if (listenersSetup) return;
  listenersSetup = true;

  try {
    console.log("[AudioStorage] Setting up event listeners...");

    _completeSubscription = BackgroundDownloader.addCompleteListener(
      (event: BGDownloadCompleteEvent) => {
        console.log(
          `[AudioStorage] Complete event received: taskId=${event.taskId}, activeDownloads=${JSON.stringify([...activeDownloads.entries()])}`,
        );
        const downloadInfo = activeDownloads.get(event.taskId);
        if (!downloadInfo) {
          console.log(
            `[AudioStorage] Ignoring complete event for unknown taskId: ${event.taskId}`,
          );
          return; // Not an audio download
        }

        handleDownloadComplete(event, downloadInfo);
      },
    );

    _errorSubscription = BackgroundDownloader.addErrorListener(
      (event: BGDownloadErrorEvent) => {
        console.log(
          `[AudioStorage] Error event received: taskId=${event.taskId}, error=${event.error}`,
        );
        const downloadInfo = activeDownloads.get(event.taskId);
        if (!downloadInfo) return; // Not an audio download

        handleDownloadError(event, downloadInfo);
      },
    );

    console.log("[AudioStorage] Event listeners set up successfully");
  } catch (error) {
    console.warn("[AudioStorage] Failed to setup event listeners:", error);
    listenersSetup = false;
  }
}

/**
 * Handle download completion
 */
async function handleDownloadComplete(
  event: BGDownloadCompleteEvent,
  downloadInfo: { itemId: string; permanent: boolean },
): Promise<void> {
  const { itemId, permanent } = downloadInfo;

  try {
    const file = new File(`file://${event.filePath}`);
    const fileInfo = file.info();
    const size = fileInfo.size || 0;

    const index = getStorageIndex();

    // Add to index
    const trackInfo: StoredTrackInfo = {
      itemId,
      localPath: event.filePath,
      size,
      storedAt: Date.now(),
      permanent,
    };

    index.tracks[itemId] = trackInfo;

    if (permanent) {
      index.totalPermanentSize += size;
    } else {
      index.totalCacheSize += size;
    }

    saveStorageIndex();

    console.log(
      `[AudioStorage] Downloaded ${itemId} (${(size / 1024 / 1024).toFixed(1)}MB, permanent=${permanent})`,
    );

    // Emit completion event
    audioStorageEvents.emit("complete", {
      itemId,
      localPath: event.filePath,
      permanent,
    });

    // Clean up tracking
    activeDownloads.delete(event.taskId);
    downloadingItems.delete(itemId);
    permanentDownloadingItems.delete(itemId);

    // Evict old cache if needed (only for cache downloads)
    if (!permanent) {
      evictCacheIfNeeded().catch(() => {
        // Ignore eviction errors
      });
    }
  } catch (error) {
    console.error(`[AudioStorage] Error handling download complete:`, error);
    activeDownloads.delete(event.taskId);
    downloadingItems.delete(itemId);
    permanentDownloadingItems.delete(itemId);
  }
}

/**
 * Handle download error
 */
function handleDownloadError(
  event: BGDownloadErrorEvent,
  downloadInfo: { itemId: string; permanent: boolean },
): void {
  const { itemId } = downloadInfo;

  console.error(`[AudioStorage] Download failed for ${itemId}:`, event.error);

  audioStorageEvents.emit("error", {
    itemId,
    error: event.error,
  });

  activeDownloads.delete(event.taskId);
  downloadingItems.delete(itemId);
  permanentDownloadingItems.delete(itemId);
}

/**
 * Get the local file path for a track if it exists
 * Checks permanent storage first, then cache
 * Returns the path WITH file:// prefix for TrackPlayer
 */
export function getLocalPath(itemId: string | undefined): string | null {
  if (!itemId) return null;

  try {
    const index = getStorageIndex();
    const info = index.tracks[itemId];

    if (info) {
      // Verify file still exists (File constructor needs file:// URI)
      try {
        const fileUri = info.localPath.startsWith("file://")
          ? info.localPath
          : `file://${info.localPath}`;
        const file = new File(fileUri);
        if (file.exists) {
          // Return the URI with file:// prefix for TrackPlayer
          return fileUri;
        }
      } catch {
        // File doesn't exist, remove from index
        if (info.permanent) {
          index.totalPermanentSize -= info.size;
        } else {
          index.totalCacheSize -= info.size;
        }
        delete index.tracks[itemId];
        saveStorageIndex();
      }
    }
  } catch {
    // Ignore errors
  }

  return null;
}

/**
 * Check if a track is currently being downloaded (any type)
 */
export function isDownloading(itemId: string | undefined): boolean {
  if (!itemId) return false;
  return downloadingItems.has(itemId);
}

/**
 * Check if a track is currently being permanently downloaded (user-initiated)
 * Use this for UI indicators - we don't want to show spinners for auto-caching
 */
export function isPermanentDownloading(itemId: string | undefined): boolean {
  if (!itemId) return false;
  return permanentDownloadingItems.has(itemId);
}

/**
 * Check if a track is permanently downloaded (not just cached)
 */
export function isPermanentlyDownloaded(itemId: string | undefined): boolean {
  if (!itemId) return false;

  try {
    const index = getStorageIndex();
    const info = index.tracks[itemId];

    if (info?.permanent) {
      // Verify file still exists
      try {
        const fileUri = info.localPath.startsWith("file://")
          ? info.localPath
          : `file://${info.localPath}`;
        const file = new File(fileUri);
        if (file.exists) {
          return true;
        }
      } catch {
        // File doesn't exist
      }
    }
  } catch {
    // Ignore errors
  }

  return false;
}

/**
 * Check if a track is cached (not permanently downloaded)
 */
export function isCached(itemId: string | undefined): boolean {
  if (!itemId) return false;

  try {
    const index = getStorageIndex();
    const info = index.tracks[itemId];

    if (info && !info.permanent) {
      // Verify file still exists
      try {
        const fileUri = info.localPath.startsWith("file://")
          ? info.localPath
          : `file://${info.localPath}`;
        const file = new File(fileUri);
        if (file.exists) {
          return true;
        }
        // File doesn't exist - clean up stale index entry
        console.log(
          `[AudioStorage] Cleaning up stale cache entry: ${itemId} (file missing)`,
        );
        index.totalCacheSize -= info.size;
        delete index.tracks[itemId];
        saveStorageIndex();
      } catch {
        // File check failed - clean up stale index entry
        index.totalCacheSize -= info.size;
        delete index.tracks[itemId];
        saveStorageIndex();
      }
    }
  } catch {
    // Ignore errors
  }

  return false;
}

/**
 * Download a track to storage
 * @param itemId - Jellyfin item ID
 * @param url - Stream URL to download from
 * @param options - Download options (permanent: true for user downloads, false for cache)
 */
export async function downloadTrack(
  itemId: string,
  url: string,
  options: DownloadOptions = { permanent: false },
): Promise<void> {
  const { permanent } = options;

  // Skip if already downloading
  if (isDownloading(itemId)) {
    return;
  }

  // Skip if already permanently downloaded
  if (isPermanentlyDownloaded(itemId)) {
    return;
  }

  // If requesting permanent download and file is only cached, delete cached version first
  if (permanent && isCached(itemId)) {
    console.log(
      `[AudioStorage] Upgrading cached track to permanent: ${itemId}`,
    );
    await deleteTrack(itemId);
  }

  // Skip if already cached and not requesting permanent
  if (!permanent && getLocalPath(itemId)) {
    return;
  }

  // Ensure listeners are set up
  setupEventListeners();

  await ensureDirectories();

  const targetDir = permanent ? permanentDir : cacheDir;

  if (!targetDir) {
    console.warn("[AudioStorage] Target directory not initialized");
    return;
  }

  // Use the actual container format as extension, fallback to m4a
  const extension = options.container?.toLowerCase() || "m4a";
  const filename = `${itemId}.${extension}`;
  const destinationPath =
    `${targetDir.uri.replace(/\/$/, "")}/${filename}`.replace("file://", "");

  console.log(
    `[AudioStorage] Starting download: ${itemId} (permanent=${permanent})`,
  );

  try {
    downloadingItems.add(itemId);
    if (permanent) {
      permanentDownloadingItems.add(itemId);
    }
    const taskId = await BackgroundDownloader.startDownload(
      url,
      destinationPath,
    );
    activeDownloads.set(taskId, { itemId, permanent });
    console.log(
      `[AudioStorage] Download started with taskId=${taskId}, tracking ${activeDownloads.size} downloads`,
    );
  } catch (error) {
    console.error(`[AudioStorage] Failed to start download:`, error);
    downloadingItems.delete(itemId);
    permanentDownloadingItems.delete(itemId);
  }
}

/**
 * Cancel a download in progress
 */
export function cancelDownload(itemId: string): void {
  for (const [taskId, info] of activeDownloads.entries()) {
    if (info.itemId === itemId) {
      try {
        BackgroundDownloader.cancelDownload(taskId);
      } catch {
        // Ignore cancel errors
      }
      activeDownloads.delete(taskId);
      downloadingItems.delete(itemId);
      permanentDownloadingItems.delete(itemId);
      console.log(`[AudioStorage] Cancelled download: ${itemId}`);
      break;
    }
  }
}

/**
 * Delete a stored track
 */
export async function deleteTrack(itemId: string): Promise<void> {
  const index = getStorageIndex();
  const info = index.tracks[itemId];

  if (!info) return;

  try {
    const file = new File(info.localPath);
    if (file.exists) {
      await file.delete();
    }
  } catch (error) {
    console.warn(`[AudioStorage] Failed to delete file:`, error);
  }

  if (info.permanent) {
    index.totalPermanentSize -= info.size;
  } else {
    index.totalCacheSize -= info.size;
  }
  delete index.tracks[itemId];
  saveStorageIndex();

  console.log(`[AudioStorage] Deleted track: ${itemId}`);
}

/**
 * Evict old cache entries if limits are exceeded
 */
async function evictCacheIfNeeded(
  maxTracks: number = DEFAULT_MAX_CACHE_TRACKS,
  maxSizeBytes: number = configuredMaxCacheSizeBytes,
): Promise<void> {
  const index = getStorageIndex();

  // Get all cache entries sorted by storedAt (oldest first)
  const cacheEntries = Object.values(index.tracks)
    .filter((t) => !t.permanent)
    .sort((a, b) => a.storedAt - b.storedAt);

  // Evict if over track limit or size limit
  while (
    cacheEntries.length > maxTracks ||
    index.totalCacheSize > maxSizeBytes
  ) {
    const oldest = cacheEntries.shift();
    if (!oldest) break;

    console.log(
      `[AudioStorage] Evicting cache entry: ${oldest.itemId} (${(oldest.size / 1024 / 1024).toFixed(1)}MB)`,
    );

    try {
      const file = new File(oldest.localPath);
      if (file.exists) {
        await file.delete();
      }
    } catch {
      // Ignore deletion errors
    }

    index.totalCacheSize -= oldest.size;
    delete index.tracks[oldest.itemId];
  }

  saveStorageIndex();
}

/**
 * Clear all cached tracks (keeps permanent downloads)
 */
export async function clearCache(): Promise<void> {
  const index = getStorageIndex();

  const cacheEntries = Object.values(index.tracks).filter((t) => !t.permanent);

  for (const entry of cacheEntries) {
    try {
      const file = new File(entry.localPath);
      if (file.exists) {
        await file.delete();
      }
    } catch {
      // Ignore deletion errors
    }
    delete index.tracks[entry.itemId];
  }

  index.totalCacheSize = 0;
  saveStorageIndex();

  console.log(`[AudioStorage] Cache cleared`);
}

/**
 * Clear all permanent downloads (keeps cache)
 */
export async function clearPermanentDownloads(): Promise<void> {
  const index = getStorageIndex();

  const permanentEntries = Object.values(index.tracks).filter(
    (t) => t.permanent,
  );

  for (const entry of permanentEntries) {
    try {
      const fileUri = entry.localPath.startsWith("file://")
        ? entry.localPath
        : `file://${entry.localPath}`;
      const file = new File(fileUri);
      if (file.exists) {
        await file.delete();
      }
    } catch {
      // Ignore deletion errors
    }
    delete index.tracks[entry.itemId];
  }

  index.totalPermanentSize = 0;
  saveStorageIndex();

  console.log(`[AudioStorage] Permanent downloads cleared`);
}

/**
 * Get storage statistics
 */
export function getStorageStats(): {
  cacheCount: number;
  cacheSize: number;
  permanentCount: number;
  permanentSize: number;
} {
  const index = getStorageIndex();
  const entries = Object.values(index.tracks);

  return {
    cacheCount: entries.filter((t) => !t.permanent).length,
    cacheSize: index.totalCacheSize,
    permanentCount: entries.filter((t) => t.permanent).length,
    permanentSize: index.totalPermanentSize,
  };
}
