package com.keylightpiano.pianotimeline.data

import android.content.Context
import com.keylightpiano.pianotimeline.data.local.AppDatabase
import com.keylightpiano.pianotimeline.data.local.RecordingDao
import com.keylightpiano.pianotimeline.data.local.toDomainOrNull
import com.keylightpiano.pianotimeline.data.remote.MusicNetwork
import com.keylightpiano.pianotimeline.domain.Recording
import com.keylightpiano.pianotimeline.player.MediaCache
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import java.io.File

/**
 * The single entry point for recording data.
 *
 * Primary source: Room database (fast, offline).
 * The SyncManager refreshes Room from the network on startup.
 *
 * The UI observes `observeRecordings()` which is a Flow from Room —
 * when SyncManager writes new data into Room, the Flow automatically
 * emits the updated list, and the UI recomposes. No manual "refresh" needed.
 */
class MusicRepository(context: Context) {

    private val dao: RecordingDao = AppDatabase.getInstance(context).recordingDao()

    /**
     * Live stream of all recordings, newest first.
     * The UI collects this as state. When Room is updated (by sync or by caching
     * a downloaded file), this Flow re-emits automatically.
     */
    fun observeRecordings(): Flow<List<Recording>> =
        dao.observeAllByDate().map { entities ->
            entities.mapNotNull { it.toDomainOrNull() }
        }

    /**
     * Mark a recording's audio as cached at the given path.
     * Called after the player successfully downloads and saves an mp3.
     */
    suspend fun setAudioCached(id: String, path: String) = withContext(Dispatchers.IO) {
        dao.setCachedAudioPath(id, path)
    }

    /**
     * Mark a recording's MIDI as cached at the given path.
     */
    suspend fun setMidiCached(id: String, path: String) = withContext(Dispatchers.IO) {
        dao.setCachedMidiPath(id, path)
    }

    /** Toggle favorite state for a recording. */
    suspend fun setFavorite(id: String, favorite: Boolean) = withContext(Dispatchers.IO) {
        // Record WHEN it was favorited so the favorites group can be ordered in
        // insertion order. 0 when unfavorited.
        val timestamp = if (favorite) System.currentTimeMillis() else 0L
        dao.setFavorite(id, favorite, timestamp)
    }

    /** Clear the "new" badge for a recording (called when play is first pressed). */
    suspend fun clearNew(id: String) = withContext(Dispatchers.IO) {
        dao.clearNew(id)
    }

    /** Re-apply the "new" badge to a recording (manual action from the long-press menu). */
    suspend fun markAsNew(id: String) = withContext(Dispatchers.IO) {
        dao.setNew(id, true)
    }

    /** Clear the "new" badge from every recording (Settings action). */
    suspend fun clearAllNew() = withContext(Dispatchers.IO) {
        dao.clearAllNew()
    }

    /**
     * Total bytes currently held in the ExoPlayer disk cache. Backed by the real
     * SimpleCache (MediaCache), same source of truth as `isFullyCached`.
     */
    suspend fun cacheSizeBytes(context: Context): Long = withContext(Dispatchers.IO) {
        MediaCache.cacheSizeBytes(context)
    }

    /**
     * Clear all cached audio/midi files from disk and reset paths in the database.
     * Used by the "clear cache" setting.
     *
     * Clears BOTH the ExoPlayer SimpleCache (where streamed audio actually lands)
     * and the legacy on-disk cache dirs, then resets the Room path columns.
     */
    suspend fun clearAllCache(context: Context) = withContext(Dispatchers.IO) {
        // Clear the real media cache (ExoPlayer SimpleCache).
        MediaCache.clear(context)

        // Delete legacy physical files, if any remain from earlier caching schemes.
        val cacheDir = audioCacheDir(context)
        if (cacheDir.exists()) {
            cacheDir.deleteRecursively()
            cacheDir.mkdirs()  // recreate empty dir
        }
        // Reset DB paths
        dao.clearAllCachedPaths()
    }

    /**
     * Build a URL for streaming/downloading a recording's audio.
     */
    fun audioUrl(recording: Recording): String =
        MusicNetwork.fileUrl(recording.fileName)

    /**
     * Build a URL for downloading a recording's MIDI file.
     */
    fun midiUrl(recording: Recording): String? =
        recording.midiFileName?.let { MusicNetwork.fileUrl(it) }

    companion object {
        /**
         * Directory where cached audio files are stored.
         * Uses the app's internal cache dir so the OS can reclaim space if needed,
         * and it's automatically deleted if the user clears app data.
         */
        fun audioCacheDir(context: Context): File =
            File(context.cacheDir, "audio").also { it.mkdirs() }

        fun midiCacheDir(context: Context): File =
            File(context.cacheDir, "midi").also { it.mkdirs() }
    }
}
