// IndexedDB wrapper for offline music storage with user isolation
const DB_NAME_PREFIX = "musique-offline"
const DB_VERSION = 3
const TRACKS_STORE = "tracks"
const AUDIO_STORE = "audio-files"
const USER_DATA_STORE = "user-data"
const CACHE_STORE = "api-cache"

interface OfflineTrack {
  id: string
  title: string
  artist: string
  album?: string
  cover_url?: string
  duration_ms: number
  audio_blob?: Blob
  downloaded_at: number
  user_id: string // Added user_id for isolation
}

interface UserData {
  user_id: string
  liked_tracks: string[]
  recently_played: string[]
}

class OfflineStorage {
  private db: IDBDatabase | null = null
  private dbReady: Promise<IDBDatabase> | null = null
  private currentUserId: string | null = null

  constructor() {
    // Don't initialize until user is set
  }

  getCurrentUserId(): string | null {
    return this.currentUserId
  }

  async initForUser(userId: string): Promise<void> {
    if (this.currentUserId === userId && this.db) {
      console.log("[v0] [OfflineStorage] Already initialized for user:", userId)
      return // Already initialized for this user
    }

    // Close existing connection
    if (this.db) {
      console.log("[v0] [OfflineStorage] Closing previous connection for user:", this.currentUserId)
      this.db.close()
      this.db = null
    }

    this.currentUserId = userId
    console.log("[v0] [OfflineStorage] Initializing IndexedDB for user:", userId)

    try {
      this.dbReady = this.initDB(userId)
      await this.dbReady
      console.log("[v0] [OfflineStorage] Successfully initialized IndexedDB for user:", userId)

      const testWrite = await this.testDatabaseAccess()
      if (!testWrite) {
        throw new Error("IndexedDB is not writable - storage might be disabled")
      }
      console.log("[v0] [OfflineStorage] Database access verified")
    } catch (error) {
      console.error("[v0] [OfflineStorage] Failed to initialize:", error)
      throw error
    }
  }

  private async initDB(userId: string): Promise<IDBDatabase> {
    return new Promise((resolve, reject) => {
      if (typeof window === "undefined") {
        reject(new Error("Not in browser environment"))
        return
      }
      
      if (!window.indexedDB) {
        // Safari in private mode doesn't support IndexedDB
        console.error("[v0] [OfflineStorage] IndexedDB not available - may be in private browsing mode")
        reject(new Error("IndexedDB not supported - try disabling private browsing"))
        return
      }

      const dbName = `${DB_NAME_PREFIX}-${userId}`
      console.log("[v0] [OfflineStorage] Opening database:", dbName, "version:", DB_VERSION)
      const request = indexedDB.open(dbName, DB_VERSION)

      request.onerror = () => reject(request.error)

      request.onsuccess = () => {
        this.db = request.result
        resolve(request.result)
      }

      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result

        // Store for track metadata
        if (!db.objectStoreNames.contains(TRACKS_STORE)) {
          const tracksStore = db.createObjectStore(TRACKS_STORE, { keyPath: "id" })
          tracksStore.createIndex("downloaded_at", "downloaded_at", { unique: false })
          tracksStore.createIndex("artist", "artist", { unique: false })
          tracksStore.createIndex("user_id", "user_id", { unique: false })
        }

        // Store for audio blobs
        if (!db.objectStoreNames.contains(AUDIO_STORE)) {
          db.createObjectStore(AUDIO_STORE, { keyPath: "id" })
        }

        // Store for user-specific data
        if (!db.objectStoreNames.contains(USER_DATA_STORE)) {
          db.createObjectStore(USER_DATA_STORE, { keyPath: "user_id" })
        }

        // Store for API response cache (genres, playlists, etc.)
        if (!db.objectStoreNames.contains(CACHE_STORE)) {
          const cacheStore = db.createObjectStore(CACHE_STORE, { keyPath: "key" })
          cacheStore.createIndex("expires_at", "expires_at", { unique: false })
        }
      }
    })
  }

  // Cache API responses for offline use
  async cacheApiResponse(key: string, data: any, ttlMs: number = 24 * 60 * 60 * 1000): Promise<void> {
    const db = await this.ensureDB()

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(CACHE_STORE, "readwrite")
      const store = transaction.objectStore(CACHE_STORE)

      store.put({
        key,
        data,
        cached_at: Date.now(),
        expires_at: Date.now() + ttlMs,
      })

      transaction.onerror = () => reject(transaction.error)
      transaction.oncomplete = () => resolve()
    })
  }

  async getCachedApiResponse<T>(key: string): Promise<{ data: T; isStale: boolean } | null> {
    const db = await this.ensureDB()

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(CACHE_STORE, "readonly")
      const store = transaction.objectStore(CACHE_STORE)
      const request = store.get(key)

      request.onerror = () => reject(request.error)
      request.onsuccess = () => {
        const result = request.result
        if (!result) {
          resolve(null)
          return
        }

        const isStale = Date.now() > result.expires_at
        resolve({ data: result.data as T, isStale })
      }
    })
  }

  async clearExpiredCache(): Promise<void> {
    const db = await this.ensureDB()
    const now = Date.now()

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(CACHE_STORE, "readwrite")
      const store = transaction.objectStore(CACHE_STORE)
      const index = store.index("expires_at")
      
      const range = IDBKeyRange.upperBound(now)
      const request = index.openCursor(range)

      request.onsuccess = (event) => {
        const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result
        if (cursor) {
          cursor.delete()
          cursor.continue()
        }
      }

      transaction.onerror = () => reject(transaction.error)
      transaction.oncomplete = () => resolve()
    })
  }

  private async testDatabaseAccess(): Promise<boolean> {
    try {
      const db = await this.ensureDB()

      return new Promise((resolve) => {
        const transaction = db.transaction(USER_DATA_STORE, "readwrite")
        const store = transaction.objectStore(USER_DATA_STORE)

        const testData = {
          user_id: `test_${Date.now()}`,
          liked_tracks: [],
          recently_played: [],
        }

        const request = store.put(testData)

        request.onsuccess = () => {
          // Clean up test data
          store.delete(testData.user_id)
          resolve(true)
        }

        request.onerror = () => {
          console.error("[v0] [OfflineStorage] Database write test failed")
          resolve(false)
        }
      })
    } catch (error) {
      console.error("[v0] [OfflineStorage] Database access test failed:", error)
      return false
    }
  }

  private async ensureDB(): Promise<IDBDatabase> {
    if (!this.dbReady) {
      throw new Error("OfflineStorage not initialized. Call initForUser first.")
    }
    return this.dbReady
  }

  getCurrentUserId(): string | null {
    return this.currentUserId
  }

  clearUserSession(): void {
    if (this.db) {
      this.db.close()
      this.db = null
    }
    this.dbReady = null
    this.currentUserId = null
  }

  async saveTrack(track: Omit<OfflineTrack, "user_id" | "downloaded_at">, audioBlob: Blob): Promise<void> {
    const db = await this.ensureDB()
    if (!this.currentUserId) throw new Error("No user logged in")

    console.log(
      "[v0] [OfflineStorage] Saving track:",
      track.title,
      "ID:", track.id,
      "User:", this.currentUserId,
      "Size:",
      (audioBlob.size / (1024 * 1024)).toFixed(2),
      "MB",
    )

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([TRACKS_STORE, AUDIO_STORE], "readwrite")

      transaction.onerror = () => {
        console.error("[v0] [OfflineStorage] Transaction error:", transaction.error)
        reject(transaction.error)
      }

      transaction.oncomplete = () => {
        console.log("[v0] [OfflineStorage] Track saved successfully:", track.title)
        resolve()
      }

      // Save track metadata with user_id
      const tracksStore = transaction.objectStore(TRACKS_STORE)
      tracksStore.put({
        ...track,
        user_id: this.currentUserId,
        downloaded_at: Date.now(),
      })

      // Save audio blob separately
      const audioStore = transaction.objectStore(AUDIO_STORE)
      audioStore.put({ id: track.id, blob: audioBlob })
    })
  }

  async getTrack(trackId: string): Promise<OfflineTrack | null> {
    const db = await this.ensureDB()

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(TRACKS_STORE, "readonly")
      const store = transaction.objectStore(TRACKS_STORE)
      const request = store.get(trackId)

      request.onerror = () => reject(request.error)
      request.onsuccess = () => resolve(request.result || null)
    })
  }

  async getAudioBlob(trackId: string): Promise<Blob | null> {
    const db = await this.ensureDB()

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(AUDIO_STORE, "readonly")
      const store = transaction.objectStore(AUDIO_STORE)
      const request = store.get(trackId)

      request.onerror = () => reject(request.error)
      request.onsuccess = () => {
        const result = request.result?.blob || null
        console.log(
          "[v0] [OfflineStorage] Audio blob for track",
          trackId,
          ":",
          result ? `${(result.size / 1024).toFixed(1)}KB` : "not found",
        )
        resolve(result)
      }
    })
  }

  async getAllTracks(): Promise<OfflineTrack[]> {
    const db = await this.ensureDB()

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(TRACKS_STORE, "readonly")
      const store = transaction.objectStore(TRACKS_STORE)
      const request = store.getAll()

      request.onerror = () => {
        console.error("[v0] [OfflineStorage] Failed to load tracks:", request.error)
        reject(request.error)
      }

      request.onsuccess = () => {
        const tracks = request.result || []
        console.log("[v0] [OfflineStorage] Loaded tracks from IndexedDB:", {
          count: tracks.length,
          tracks: tracks.map((t) => ({ id: t.id, title: t.title })),
        })
        resolve(tracks)
      }
    })
  }

  async deleteTrack(trackId: string): Promise<void> {
    const db = await this.ensureDB()

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([TRACKS_STORE, AUDIO_STORE], "readwrite")

      transaction.onerror = () => reject(transaction.error)
      transaction.oncomplete = () => resolve()

      transaction.objectStore(TRACKS_STORE).delete(trackId)
      transaction.objectStore(AUDIO_STORE).delete(trackId)
    })
  }

  async isTrackDownloaded(trackId: string): Promise<boolean> {
    try {
      const track = await this.getTrack(trackId)
      return track !== null
    } catch {
      return false
    }
  }

  async getStorageUsed(): Promise<number> {
    const db = await this.ensureDB()

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(AUDIO_STORE, "readonly")
      const store = transaction.objectStore(AUDIO_STORE)
      const request = store.getAll()

      request.onerror = () => reject(request.error)
      request.onsuccess = () => {
        const blobs = request.result || []
        const totalSize = blobs.reduce((acc: number, item: { blob: Blob }) => acc + (item.blob?.size || 0), 0)
        resolve(totalSize)
      }
    })
  }

  async clearAll(): Promise<void> {
    const db = await this.ensureDB()

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([TRACKS_STORE, AUDIO_STORE, USER_DATA_STORE], "readwrite")

      transaction.onerror = () => reject(transaction.error)
      transaction.oncomplete = () => resolve()

      transaction.objectStore(TRACKS_STORE).clear()
      transaction.objectStore(AUDIO_STORE).clear()
      transaction.objectStore(USER_DATA_STORE).clear()
    })
  }

  async saveUserData(data: { liked_tracks?: string[]; recently_played?: string[] }): Promise<void> {
    const db = await this.ensureDB()
    if (!this.currentUserId) throw new Error("No user logged in")

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(USER_DATA_STORE, "readwrite")
      const store = transaction.objectStore(USER_DATA_STORE)

      const request = store.get(this.currentUserId)

      request.onsuccess = () => {
        const existing = request.result || { user_id: this.currentUserId, liked_tracks: [], recently_played: [] }
        const updated = {
          ...existing,
          ...data,
        }
        store.put(updated)
      }

      transaction.onerror = () => reject(transaction.error)
      transaction.oncomplete = () => resolve()
    })
  }

  async loadUserData(): Promise<UserData | null> {
    const db = await this.ensureDB()
    if (!this.currentUserId) return null

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(USER_DATA_STORE, "readonly")
      const store = transaction.objectStore(USER_DATA_STORE)
      const request = store.get(this.currentUserId)

      request.onerror = () => reject(request.error)
      request.onsuccess = () => resolve(request.result || null)
    })
  }

  async downloadTrackForOffline(
    track: {
      id: string
      title: string
      artist: string
      album?: string
      cover_url?: string
      duration_ms: number
      audio_url: string
    },
    quality?: "low" | "normal" | "high" | "very_high",
    onProgress?: (progress: number) => void,
  ): Promise<boolean> {
    console.log("[v0] [OfflineStorage.downloadTrackForOffline] Starting download:", track.title)

    try {
      const audioUrl = quality ? `/api/stream/${track.id}?quality=${quality}` : track.audio_url
      console.log("[v0] [OfflineStorage.downloadTrackForOffline] Fetching from:", audioUrl)

      // Fetch audio file
      const response = await fetch(audioUrl)
      if (!response.ok) {
        console.error("[v0] [OfflineStorage.downloadTrackForOffline] Fetch failed:", response.status)
        throw new Error(`Failed to fetch audio: ${response.status}`)
      }

      const contentLength = response.headers.get("content-length")
      const total = contentLength ? Number.parseInt(contentLength, 10) : 0
      console.log("[v0] [OfflineStorage.downloadTrackForOffline] Total size:", (total / (1024 * 1024)).toFixed(2), "MB")

      const reader = response.body?.getReader()
      if (!reader) throw new Error("No reader available")

      const chunks: Uint8Array[] = []
      let received = 0

      while (true) {
        const { done, value } = await reader.read()
        if (done) break

        chunks.push(value)
        received += value.length

        if (total > 0 && onProgress) {
          const progress = (received / total) * 100
          onProgress(progress)
          if (progress % 25 < 1) {
            console.log("[v0] [OfflineStorage.downloadTrackForOffline] Progress:", Math.round(progress), "%")
          }
        }
      }

      const audioBlob = new Blob(chunks, { type: "audio/mpeg" })
      console.log("[v0] [OfflineStorage.downloadTrackForOffline] Download complete, saving to IndexedDB")

      // Save to IndexedDB
      await this.saveTrack(
        {
          id: track.id,
          title: track.title,
          artist: track.artist,
          album: track.album,
          cover_url: track.cover_url,
          duration_ms: track.duration_ms,
        },
        audioBlob,
      )

      console.log("[v0] [OfflineStorage.downloadTrackForOffline] Successfully saved to offline storage")
      return true
    } catch (error) {
      console.error("[v0] [OfflineStorage.downloadTrackForOffline] Failed:", error)
      return false
    }
  }
}

// Singleton instance
export const offlineStorage = typeof window !== "undefined" ? new OfflineStorage() : null

// Helper to download a track for offline use
export async function downloadTrackForOffline(
  track: {
    id: string
    title: string
    artist: string
    album?: string
    cover_url?: string
    duration_ms: number
    audio_url: string
  },
  onProgress?: (progress: number) => void,
  quality?: "low" | "normal" | "high" | "very_high",
): Promise<boolean> {
  if (!offlineStorage) {
    console.error("[v0] [downloadTrackForOffline] Offline storage not available")
    return false
  }

  // Ensure storage is initialized
  if (!offlineStorage.getCurrentUserId()) {
    console.error("[v0] [downloadTrackForOffline] Storage not initialized - call offlineStorage.initForUser first")
    return false
  }

  console.log("[v0] [downloadTrackForOffline] Starting download:", track.title, "for user:", offlineStorage.getCurrentUserId())

  try {
    const audioUrl = quality ? `/api/stream/${track.id}?quality=${quality}` : track.audio_url
    console.log("[v0] [downloadTrackForOffline] Fetching from:", audioUrl)

    // Fetch audio file
    const response = await fetch(audioUrl)
    if (!response.ok) {
      console.error("[v0] [downloadTrackForOffline] Fetch failed:", response.status)
      throw new Error("Failed to fetch audio")
    }

    const contentLength = response.headers.get("content-length")
    const total = contentLength ? Number.parseInt(contentLength, 10) : 0
    console.log("[v0] [downloadTrackForOffline] Total size:", (total / (1024 * 1024)).toFixed(2), "MB")

    const reader = response.body?.getReader()
    if (!reader) throw new Error("No reader available")

    const chunks: Uint8Array[] = []
    let received = 0

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      chunks.push(value)
      received += value.length

      if (total > 0 && onProgress) {
        const progress = (received / total) * 100
        onProgress(progress)
        if (progress % 25 < 1) {
          // Log every 25%
          console.log("[v0] [downloadTrackForOffline] Progress:", Math.round(progress), "%")
        }
      }
    }

    const audioBlob = new Blob(chunks, { type: "audio/mpeg" })
    console.log("[v0] [downloadTrackForOffline] Download complete, saving to IndexedDB")

    // Save to IndexedDB
    await offlineStorage.saveTrack(
      {
        id: track.id,
        title: track.title,
        artist: track.artist,
        album: track.album,
        cover_url: track.cover_url,
        duration_ms: track.duration_ms,
      },
      audioBlob,
    )

    console.log("[v0] [downloadTrackForOffline] Successfully saved to offline storage")
    return true
  } catch (error) {
    console.error("[v0] [downloadTrackForOffline] Failed:", error)
    return false
  }
}

// Get offline audio URL (creates blob URL from IndexedDB)
export async function getOfflineAudioUrl(trackId: string): Promise<string | null> {
  if (!offlineStorage) return null

  try {
    const blob = await offlineStorage.getAudioBlob(trackId)
    if (!blob) return null

    return URL.createObjectURL(blob)
  } catch {
    return null
  }
}
