package com.lazy.emulate.data.repository

import android.content.Context
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import com.lazy.emulate.data.CoverArtManager
import com.lazy.emulate.data.PreferencesManager
import com.lazy.emulate.data.model.Game
import com.lazy.emulate.emulation.ConsoleType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.io.File
import java.util.UUID

class GameRepository(private val context: Context) {

    private val coverArtManager = CoverArtManager(context)
    private val preferencesManager = PreferencesManager(context)
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    private val _games = MutableStateFlow<List<Game>>(emptyList())
    val games: StateFlow<List<Game>> = _games.asStateFlow()

    private val gamesRoot = File(Environment.getExternalStorageDirectory(), "Games")

    // Maps folder names to console types
    private val folderToConsole = mapOf(
        "PS" to ConsoleType.PS1,
        "PS1" to ConsoleType.PS1,
        "PS2" to ConsoleType.PS2,
        "NES" to ConsoleType.NES,
        "SNES" to ConsoleType.SNES,
        "N64" to ConsoleType.N64,
        "Genesis" to ConsoleType.GENESIS,
        "GEN" to ConsoleType.GENESIS,
    )

    fun scanGameFolders() {
        if (!gamesRoot.exists()) return

        val scanned = mutableListOf<Game>()
        val existingPaths = _games.value.map { it.romPath }.toSet()
        val favorites = preferencesManager.getFavorites()

        gamesRoot.listFiles()?.forEach { folder ->
            if (!folder.isDirectory) return@forEach
            val consoleType = folderToConsole[folder.name.uppercase()]
                ?: folderToConsole[folder.name]
                ?: return@forEach

            val files = folder.listFiles()?.filter { it.isFile } ?: return@forEach
            // For disc-based consoles, prefer .cue files and skip companion .bin files
            val cueFiles = files.filter { it.extension.lowercase() == "cue" }
            val cueBasenames = cueFiles.map { it.nameWithoutExtension.lowercase() }.toSet()

            files.forEach { file ->
                if (file.absolutePath in existingPaths) return@forEach
                val ext = file.extension.lowercase()
                // Skip .bin files that belong to a .cue (exact match or multi-track)
                if (ext == "bin") {
                    val binName = file.nameWithoutExtension.lowercase()
                    if (cueBasenames.any { binName == it || binName.startsWith("$it ") }) {
                        return@forEach
                    }
                }
                if (ext in consoleType.fileExtensions || ext == "zip" || ext == "7z") {
                    val rawTitle = file.nameWithoutExtension
                    val displayTitle = CoverArtManager.cleanTitle(rawTitle)
                    val cachedArt = coverArtManager.getCachedPath(rawTitle, consoleType)
                    scanned.add(
                        Game(
                            id = UUID.randomUUID().toString(),
                            title = displayTitle,
                            rawTitle = rawTitle,
                            romPath = file.absolutePath,
                            consoleType = consoleType,
                            coverArtPath = cachedArt,
                            favorite = file.absolutePath in favorites
                        )
                    )
                }
            }
        }

        if (scanned.isNotEmpty()) {
            _games.value = (_games.value + scanned).sortedBy { it.title.lowercase() }
            fetchMissingCoverArt()
        }
    }

    private fun fetchMissingCoverArt() {
        val gamesNeedingArt = _games.value.filter { it.coverArtPath == null }
        if (gamesNeedingArt.isEmpty()) return

        // Concurrency was 8 — combined with parallel bitmap decodes that pinned the heap and
        // caused OOM crashes on launch. 2 is plenty for a background fetch.
        val semaphore = Semaphore(2)
        scope.launch {
            gamesNeedingArt.map { game ->
                async {
                    semaphore.withPermit {
                        try {
                            val path = coverArtManager.fetchCoverArt(game.rawTitle, game.consoleType)
                            if (path != null) {
                                _games.value = _games.value.map {
                                    if (it.id == game.id) it.copy(coverArtPath = path) else it
                                }
                            }
                        } catch (oom: OutOfMemoryError) {
                            // Don't bring the whole app down for one bad cover art fetch.
                            android.util.Log.w("GameRepository", "OOM fetching cover for ${game.title}")
                        } catch (t: Throwable) {
                            android.util.Log.w("GameRepository", "cover fetch failed for ${game.title}: ${t.message}")
                        }
                    }
                }
            }.awaitAll()
        }
    }

    fun addGame(title: String, romPath: String, consoleType: ConsoleType): Game {
        val game = Game(
            id = UUID.randomUUID().toString(),
            title = title,
            romPath = romPath,
            consoleType = consoleType
        )
        _games.value = _games.value + game
        return game
    }

    fun addGameFromUri(uri: Uri, consoleType: ConsoleType): Game? {
        val fileName = getFileName(uri) ?: return null
        val title = fileName.substringBeforeLast(".")
        return addGame(title, uri.toString(), consoleType)
    }

    fun removeGame(gameId: String) {
        _games.value = _games.value.filter { it.id != gameId }
    }

    fun toggleFavorite(gameId: String) {
        _games.value = _games.value.map { game ->
            if (game.id == gameId) {
                val newFav = !game.favorite
                preferencesManager.setFavorite(game.romPath, newFav)
                game.copy(favorite = newFav)
            } else game
        }
    }

    fun updateLastPlayed(gameId: String) {
        _games.value = _games.value.map { game ->
            if (game.id == gameId) game.copy(lastPlayed = System.currentTimeMillis()) else game
        }
    }

    fun getGamesByConsole(consoleType: ConsoleType): List<Game> {
        return _games.value.filter { it.consoleType == consoleType }
    }

    private fun getFileName(uri: Uri): String? {
        return context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
            val nameIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
            if (cursor.moveToFirst() && nameIndex >= 0) cursor.getString(nameIndex) else null
        }
    }
}
