@file:OptIn(ExperimentalTime::class)
@file:Suppress("HardCodedStringLiteral", "unused")

package eu.gaudian.translator.model.repository

import android.content.Context
import androidx.room.withTransaction
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.TagCategory
import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.model.VocabularyFilter
import eu.gaudian.translator.model.VocabularyItem
import eu.gaudian.translator.model.VocabularyItemState
import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.model.db.AppDatabase
import eu.gaudian.translator.model.db.CategoryMappingEntity
import eu.gaudian.translator.model.db.DailyStatEntity
import eu.gaudian.translator.model.db.StageMappingEntity
import eu.gaudian.translator.model.db.VocabularyCategoryEntity
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.VocabularyService
import eu.gaudian.translator.viewmodel.CategoryProgress
import eu.gaudian.translator.viewmodel.StageStats
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.minus
import kotlinx.datetime.plus
import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlin.time.Instant

private const val TAG = "VocabularyRepository"

@Serializable
data class CategoryMapping(
    val vocabularyItemId: Int,
    val categoryId: Int,
)

class VocabularyRepository private constructor(context: Context) {
    companion object {
        @Volatile private var INSTANCE: VocabularyRepository? = null
        fun getInstance(context: Context): VocabularyRepository = INSTANCE ?: synchronized(this) {
            INSTANCE ?: VocabularyRepository(context.applicationContext).also { INSTANCE = it }
        }
    }

    val settingsRepository = SettingsRepository(context)
    private val vocabularyItemService = VocabularyService(context)
    private val updateMappingsMutex = Mutex()

    // Coalescing scheduler for updateMappings to avoid heavy bursts
    private val repoScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
    private val debounceMs = 1500L
    @Volatile private var isRunning = false
    @Volatile private var pendingRequest = false
    private var debounceJob: Job? = null

    // DAOs from Room are the new data source
    private val db = AppDatabase.getDatabase(context)
    private val itemDao = db.vocabularyItemDao()
    private val stateDao = db.vocabularyStateDao()
    private val categoryDao = db.categoryDao()
    private val mappingDao = db.mappingDao()
    private val dailyStatDao = db.dailyStatDao()

     fun initializeRepository() {
        Log.d(TAG, "Initializing repository...")
        requestUpdateMappings()
    }

    suspend fun getDailyVocabularyStats(startDate: LocalDate, endDate: LocalDate): Map<LocalDate, Int> {
        // The DAO query does all the hard work
        val statsFromDb = stateDao.getCorrectAnswerCountsByDate(startDate, endDate)
        val dailyStats = statsFromDb.associate { it.date to it.count }.toMutableMap()

        // Ensure all dates in the range are present, even if their count is 0
        var currentDate = startDate
        while (currentDate <= endDate) {
            dailyStats.putIfAbsent(currentDate, 0)
            currentDate = currentDate.plus(1, DateTimeUnit.DAY)
        }
        return dailyStats
    }

    fun getCategoryMappingsFlow(): Flow<List<CategoryMapping>> {
        return mappingDao.getCategoryMappingsFlow().map { list ->
            list.map { CategoryMapping(it.vocabularyItemId, it.categoryId) }
        }
    }

    suspend fun getWordsLearnedByDate(startDate: LocalDate, endDate: LocalDate): Map<LocalDate, Int> {
        val allStates = getAllVocabularyItemStates()
        val dailyStats = mutableMapOf<LocalDate, Int>().withDefault { 0 }

        allStates.forEach { state ->
            if (state.correctAnswerCount >= settingsRepository.criteriaCorrect.flow.first()) {
                state.lastCorrectAnswer?.let { learnedTime ->
                    val learnedDate = learnedTime.toLocalDateTime(TimeZone.currentSystemDefault()).date
                    if (learnedDate in startDate..endDate) {
                        dailyStats[learnedDate] = dailyStats.getValue(learnedDate) + 1
                    }
                }
            }
        }

        var currentDate = startDate
        while (currentDate <= endDate) {
            dailyStats.putIfAbsent(currentDate, 0)
            currentDate = currentDate.plus(1, DateTimeUnit.DAY)
        }
        return dailyStats
    }

    private suspend fun runUpdateMappingsInternal() = updateMappingsMutex.withLock {
        coroutineScope {
            val stageUpdateJob = async { actualizeVocabularyStageMappings() }
            stageUpdateJob.await()

            val listMappings = async { calculateListMappings() }.await()
            val filterMappings = async { calculateFilterMappings() }.await()

            val allMappings = (listMappings + filterMappings).distinct()

            // Atomically replace all mappings
            mappingDao.setAllCategoryMappings(allMappings.map { CategoryMappingEntity(it.vocabularyItemId, it.categoryId) })
        }
    }

    // Public scheduler entry
    private fun requestUpdateMappings() {
        // Mark that a request is pending
        pendingRequest = true
        // Restart debounce timer
        debounceJob?.cancel()
        debounceJob = repoScope.launch {
            delay(debounceMs)
            triggerIfNeeded()
        }
    }

    private fun triggerIfNeeded() {
        // Only one runner at a time; if already running, we'll run again afterwards
        if (isRunning) return
        if (!pendingRequest) return
        pendingRequest = false
        isRunning = true
        repoScope.launch {
            try {
                runUpdateMappingsInternal()
            } finally {
                isRunning = false
                // If more requests arrived while running, schedule next run after small debounce
                if (pendingRequest) {
                    debounceJob?.cancel()
                    debounceJob = repoScope.launch {
                        delay(debounceMs)
                        triggerIfNeeded()
                    }
                }
            }
        }
    }

    suspend fun editVocabularyItem(vocabularyItem: VocabularyItem) {
        Log.d(TAG, "editVocabularyItem: Editing item id=${vocabularyItem.id}")
        itemDao.upsertItem(vocabularyItem)
        requestUpdateMappings()
    }

    suspend fun updateVocabularyItems(items: List<VocabularyItem>) {
        Log.d(TAG, "updateVocabularyItems: Updating ${items.size} items.")
        items.forEach { itemDao.upsertItem(it) }
        requestUpdateMappings()
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    fun getDueTodayItemsFlow(): Flow<List<VocabularyItem>> {
        return combine(
            settingsRepository.intervalStage1.flow, settingsRepository.intervalStage2.flow,
            settingsRepository.intervalStage3.flow, settingsRepository.intervalStage4.flow,
            settingsRepository.intervalStage5.flow, settingsRepository.intervalLearned.flow
        ) { intervals ->
            // This is a list of integers [stage1, stage2, ...]
            intervals.toList()
        }.flatMapLatest { intervals ->
            // flatMapLatest ensures the DB query is re-triggered when intervals change
            itemDao.getDueTodayItemsFlow(
                now = Clock.System.now().epochSeconds,
                intervalStage1 = intervals[0],
                intervalStage2 = intervals[1],
                intervalStage3 = intervals[2],
                intervalStage4 = intervals[3],
                intervalStage5 = intervals[4],
                intervalLearned = intervals[5]
            )
        }
    }

    fun getAllVocabularyItemsFlow(): Flow<List<VocabularyItem>> = itemDao.getAllItemsFlow()

    suspend fun getAllVocabularyItems(): List<VocabularyItem> = itemDao.getAllItems()

    suspend fun getVocabularyItemById(vocabularyItemId: Int): VocabularyItem? = itemDao.getItemById(vocabularyItemId)

    suspend fun deleteVocabularyItemById(vocabularyItemId: Int) {
        Log.w(TAG, "deleteVocabularyItemById: Deleting item id=$vocabularyItemId")
        itemDao.deleteItemById(vocabularyItemId)
        requestUpdateMappings()
    }

    suspend fun deleteVocabularyItemsByIds(vocabularyItemIds: List<Int>) {
        Log.w(TAG, "deleteVocabularyItemsByIds: Deleting ${vocabularyItemIds.size} items.")
        itemDao.deleteItemsByIds(vocabularyItemIds)
        requestUpdateMappings()
    }

    suspend fun generateVocabularyItems(
        category: String, languageFirst: Language, languageSecond: Language, amount: Int
    ): Result<List<VocabularyItem>> {
        return vocabularyItemService.generateVocabularyItems(category, languageFirst, languageSecond, amount)
    }

    suspend fun introduceVocabularyItems(newItems: List<VocabularyItem>, categoryIds: List<Int> = emptyList()) {
        Log.i(TAG, "introduceVocabularyItems: Adding ${newItems.size} new items to categories: $categoryIds")
        val maxId = itemDao.getMaxItemId() ?: 0
        val updatedItems = newItems.mapIndexed { index, item -> item.copy(id = maxId + index + 1) }
        itemDao.insertAll(updatedItems)

        if (categoryIds.isNotEmpty()) {
            val newMappings = updatedItems.flatMap { item ->
                categoryIds.map { categoryId ->
                    CategoryMappingEntity(vocabularyItemId = item.id, categoryId = categoryId)
                }
            }
            newMappings.forEach { mappingDao.addCategoryMapping(it) }
        }
        requestUpdateMappings()
    }

    @Suppress("unused")
    suspend fun findDuplicates(): List<VocabularyItem> {
        Log.d(TAG, "findDuplicates: Searching for duplicate items.")
        return getAllVocabularyItems()
            .groupBy { item -> item.wordFirst.lowercase() to item.wordSecond.lowercase() }
            .values
            .filter { it.size > 1 }
            .flatten()
    }

    suspend fun cleanDuplicates() {
        Log.i(TAG, "cleanDuplicates: Starting duplicate cleanup.")
        val allItems = getAllVocabularyItems()
        val uniqueItems = allItems.distinctBy { item -> item.wordFirst.lowercase() to item.wordSecond.lowercase() }
        if (allItems.size != uniqueItems.size) {
            val itemsToDelete = allItems.filterNot { uniqueItem -> uniqueItems.any { it.id == uniqueItem.id } }
            Log.w(TAG, "cleanDuplicates: Found and deleting ${itemsToDelete.size} duplicates.")
            itemDao.deleteItemsByIds(itemsToDelete.map { it.id })
            requestUpdateMappings()
        } else {
            Log.d(TAG, "cleanDuplicates: No duplicates found.")
        }
    }

    private fun mapEntityToCategory(entity: VocabularyCategoryEntity): VocabularyCategory {
        return when(entity.type) {
            "LIST", "TAG" -> TagCategory(entity.id, entity.name)
            "FILTER" -> {
                val langsJson = entity.filterLanguages
                val langs: List<Int>? = langsJson?.let { Json.decodeFromString(it) }
                val pair: Pair<Int, Int>? = if (entity.dictLangFirst != null && entity.dictLangSecond != null) {
                    Pair(entity.dictLangFirst, entity.dictLangSecond)
                } else null
                VocabularyFilter(
                    id = entity.id,
                    name = entity.name,
                    languages = langs,
                    languagePairs = pair,
                    stages = entity.filterStages?.let { Json.decodeFromString(it) }
                )
            }
            "DICTIONARY" -> VocabularyFilter(
                id = entity.id,
                name = entity.name,
                languages = null,
                languagePairs = Pair(entity.dictLangFirst!!, entity.dictLangSecond!!),
                stages = null
            )
            else -> TagCategory(entity.id, entity.name)
        }
    }

    private suspend fun mapCategoryToEntity(category: VocabularyCategory): VocabularyCategoryEntity {
        val id = if (category.id == 0) (categoryDao.getAllCategories().maxOfOrNull { it.id } ?: 0) + 1 else category.id
        return when(category) {
            is TagCategory -> VocabularyCategoryEntity(id, category.name, "TAG", null, null, null, null)
            is VocabularyFilter -> {
                Log.d(TAG, "mapCategoryToEntity: $category, id=$id")
                val hasPair = category.languagePairs != null
                VocabularyCategoryEntity(
                    id = id,
                    name = category.name,
                    type = "FILTER",
                    filterLanguages = if (!hasPair) category.languages?.let { Json.encodeToString(it) } else null,
                    filterStages = category.stages?.let { if (it.isEmpty()) null else Json.encodeToString(it) },
                    dictLangFirst = category.languagePairs?.first,
                    dictLangSecond = category.languagePairs?.second
                )
            }
        }
    }

    suspend fun getAllCategories(): List<VocabularyCategory> {
        return categoryDao.getAllCategories().map { mapEntityToCategory(it) }
    }

    fun getAllCategoriesFlow(): Flow<List<VocabularyCategory>> {
        return categoryDao.getAllCategoriesFlow().map { list -> list.map { mapEntityToCategory(it) } }
    }

    suspend fun saveCategory(category: VocabularyCategory) {
        val entity = mapCategoryToEntity(category)
        Log.i(TAG, "saveCategory: Upserting category='${category.name}' -> entity=${entity}")
        categoryDao.upsertCategory(entity)
        // Recalculate mappings after saving the updated category to ensure filters (languages/stages/pair) take effect immediately
        requestUpdateMappings()
    }

    suspend fun deleteCategoryById(categoryId: Int) {
        Log.w(TAG, "deleteCategoryById: Deleting category id=$categoryId")
        categoryDao.deleteCategoryById(categoryId)
        requestUpdateMappings()
    }

    suspend fun getCategoryMappings(): List<CategoryMapping> {
        return mappingDao.getCategoryMappings().map { CategoryMapping(it.vocabularyItemId, it.categoryId) }
    }

    suspend fun addVocabularyItemToList(vocabularyItemId: Int, listId: Int) {
        Log.d(TAG, "addVocabularyItemToList: Adding item $vocabularyItemId to list $listId")
        mappingDao.addCategoryMapping(CategoryMappingEntity(vocabularyItemId, listId))
    }

    suspend fun removeVocabularyItemFromList(vocabularyItemId: Int, listId: Int) {
        Log.d(TAG, "removeVocabularyItemFromList: Removing item $vocabularyItemId from list $listId")
        mappingDao.removeCategoryMapping(vocabularyItemId, listId)
    }

    suspend fun getVocabularyItemsByCategory(categoryId: Int): List<VocabularyItem> {
        return itemDao.getItemsByCategoryId(categoryId)
    }

    @Suppress("unused")
    fun getAllVocabularyItemStatesFlow(): Flow<List<VocabularyItemState>> = stateDao.getAllStatesFlow()

    suspend fun getAllVocabularyItemStates(): List<VocabularyItemState> = stateDao.getAllStates()

    suspend fun getVocabularyItemStateById(vocabularyItemId: Int): VocabularyItemState? = stateDao.getStateById(vocabularyItemId)

    suspend fun saveVocabularyItemState(vocabularyItemState: VocabularyItemState) {
        Log.d(TAG, "saveVocabularyItemState: Saving state for item ${vocabularyItemState.vocabularyItemId}")
        stateDao.upsertState(vocabularyItemState)
    }

    @Suppress("unused")
    suspend fun itemExists(word: String, languageID: Int?): Boolean {
        // Avoid loading all items into memory; delegate existence check to the DB
        return itemDao.itemExists(word, languageID)
    }

    suspend fun getLastCorrectAnswer(vocabularyItemId: Int): Instant? {
        return getVocabularyItemStateById(vocabularyItemId)?.lastCorrectAnswer
    }

    suspend fun getLastIncorrectAnswer(vocabularyItemId: Int): Instant? {
        return getVocabularyItemStateById(vocabularyItemId)?.lastIncorrectAnswer
    }

    suspend fun getCorrectAnswerCount(vocabularyItemId: Int): Int {
        return getVocabularyItemStateById(vocabularyItemId)?.correctAnswerCount ?: 0
    }

    suspend fun getIncorrectAnswerCount(vocabularyItemId: Int): Int {
        return getVocabularyItemStateById(vocabularyItemId)?.incorrectAnswerCount ?: 0
    }

    @Suppress("unused")
    suspend fun getVocabularyItemsByStage(stage: VocabularyStage): List<VocabularyItem> {
        val stageMapping = loadStageMapping().first()
        val idsForStage = stageMapping.filterValues { it == stage }.keys
        if (idsForStage.isEmpty()) return emptyList()
        return itemDao.getItemsByIds(idsForStage.toList())
    }

    suspend fun changeVocabularyItemStage(item: VocabularyItem?, newStage: VocabularyStage) {
        val itemId = item?.id ?: return
        Log.d(TAG, "changeVocabularyItemStage: Changing item $itemId to stage $newStage")
        mappingDao.upsertStageMapping(StageMappingEntity(itemId, newStage))
    }

    suspend fun changeVocabularyItemsStage(items: List<VocabularyItem>?, newStage: VocabularyStage) {
        if (items.isNullOrEmpty()) return
        Log.d(TAG, "changeVocabularyItemsStage: Changing ${items.size} items to stage $newStage")
        val newMappings = items.map { StageMappingEntity(it.id, newStage) }
        mappingDao.upsertStageMappings(newMappings)
    }

    fun getVocabularyItemStage(itemId: Int): Flow<VocabularyStage> {
        return loadStageMapping().map { stageMap ->
            stageMap[itemId] ?: VocabularyStage.NEW
        }
    }

    fun loadStageMapping(): Flow<Map<Int, VocabularyStage>> {
        return mappingDao.getStageMappingsFlow().map { list ->
            list.associate { it.vocabularyItemId to it.stage }
        }
    }

    private suspend fun saveStageMapping(mapping: Map<Int, VocabularyStage>) {
        val entities = mapping.map { StageMappingEntity(it.key, it.value) }
        mappingDao.upsertStageMappings(entities)
    }

    suspend fun updateFlashcardStage(item: VocabularyItem, isCorrect: Boolean) {
        val vocabularyItemId = item.id
        val currentStage = getVocabularyItemStage(item.id).first()
        val vocabularyItemState = getVocabularyItemStateById(vocabularyItemId) ?: VocabularyItemState(vocabularyItemId)
        val criteriaCorrect = settingsRepository.criteriaCorrect.flow.first()
        val criteriaWrong = settingsRepository.criteriaWrong.flow.first()
        val now = Clock.System.now()
        Log.d(TAG, "updateFlashcardStage: Item=${item.id}, Correct=$isCorrect, CurrentStage=$currentStage")


        if (isCorrect) {
            vocabularyItemState.correctAnswerCount++
            vocabularyItemState.lastCorrectAnswer = now
        } else {
            vocabularyItemState.incorrectAnswerCount++
            vocabularyItemState.lastIncorrectAnswer = now
        }

        val nextStage = calculateNextStage(
            currentStage, isCorrect,
            vocabularyItemState.correctAnswerCount, vocabularyItemState.incorrectAnswerCount,
            criteriaCorrect, criteriaWrong
        )

        if (nextStage != currentStage) {
            Log.i(TAG, "updateFlashcardStage: Item ${item.id} moved from $currentStage to $nextStage")
            vocabularyItemState.correctAnswerCount = 0
            vocabularyItemState.incorrectAnswerCount = 0
            changeVocabularyItemStage(item, nextStage)
        }
        saveVocabularyItemState(vocabularyItemState)
    }

    private fun calculateNextStage(
        currentStage: VocabularyStage, isCorrect: Boolean,
        correctCount: Int, incorrectCount: Int,
        criteriaCorrect: Int, criteriaWrong: Int
    ): VocabularyStage {
        if (isCorrect) {
            // Correct answer: advance to next stage if criteria met
            val readyToAdvance = correctCount >= criteriaCorrect
            if (!readyToAdvance) return currentStage
            return when (currentStage) {
                VocabularyStage.NEW -> VocabularyStage.STAGE_1
                VocabularyStage.STAGE_1 -> VocabularyStage.STAGE_2
                VocabularyStage.STAGE_2 -> VocabularyStage.STAGE_3
                VocabularyStage.STAGE_3 -> VocabularyStage.STAGE_4
                VocabularyStage.STAGE_4 -> VocabularyStage.STAGE_5
                VocabularyStage.STAGE_5 -> VocabularyStage.LEARNED
                VocabularyStage.LEARNED -> VocabularyStage.LEARNED
            }
        } else {
            // Incorrect answer: demote to previous stage if criteria met
            val readyToDemote = incorrectCount >= criteriaWrong
            if (!readyToDemote) return currentStage
            return when (currentStage) {
                VocabularyStage.LEARNED -> VocabularyStage.STAGE_5
                VocabularyStage.STAGE_5 -> VocabularyStage.STAGE_4
                VocabularyStage.STAGE_4 -> VocabularyStage.STAGE_3
                VocabularyStage.STAGE_3 -> VocabularyStage.STAGE_2
                VocabularyStage.STAGE_2 -> VocabularyStage.STAGE_1
                VocabularyStage.STAGE_1 -> VocabularyStage.STAGE_1
                VocabularyStage.NEW -> VocabularyStage.NEW
            }
        }
    }

    private fun isItemFitForCategory(
        item: VocabularyItem,
        filter: VocabularyFilter,
        stageMapping: Map<Int, VocabularyStage>
    ): Boolean {
        val stage = stageMapping[item.id] ?: VocabularyStage.NEW
        val stageFilter = filter.stages
        val stageMatches = stageFilter.isNullOrEmpty() || stageFilter.contains(stage)

        // Language filter precedence: dictionary pair > languages list > no language filter
        val firstId = item.languageFirstId
        val secondId = item.languageSecondId
        val languageMatches = when {
            // Pair specified: both item language IDs must be non-null and match the pair (in any order)
            filter.languagePairs != null -> {
                val (a, b) = filter.languagePairs
                (firstId != null && secondId != null) &&
                        ((firstId == a && secondId == b) || (firstId == b && secondId == a))
            }
            // Languages list specified: any of the item language IDs must be in the list
            !filter.languages.isNullOrEmpty() -> {
                val ids = filter.languages
                (firstId != null && ids.contains(firstId)) || (secondId != null && ids.contains(secondId))
            }
            else -> true
        }
        val matches = stageMatches && languageMatches
        return matches
    }

    suspend fun calcAvailableDictionaries(): Set<Pair<Int, Int>> {
        return getAllVocabularyItems().mapNotNull {
            val lang1 = it.languageFirstId
            val lang2 = it.languageSecondId
            if (lang1 != null && lang2 != null) {
                if (lang1 < lang2) lang1 to lang2 else lang2 to lang1
            } else {
                null
            }
        }.toSet()
    }

    suspend fun getCategoryById(categoryId: Int): VocabularyCategory? {
        return categoryDao.getCategoryById(categoryId)?.let { mapEntityToCategory(it) }
    }

    private suspend fun calculateListMappings(): List<CategoryMapping> {
        val allItemIds = getAllVocabularyItems().map { it.id }.toSet()
        val listIds = getAllCategories().filterIsInstance<TagCategory>().map { it.id }.toSet()
        val listMappings = getCategoryMappings().filter { it.categoryId in listIds && it.vocabularyItemId in allItemIds }
        Log.d(TAG, "calculateListMappings: Found ${listMappings.size} manual list mappings.")
        return listMappings
    }

    private suspend fun calculateFilterMappings(): List<CategoryMapping> {
        val vocabularyItems = getAllVocabularyItems()
        val autoFilters = getAllCategories().filterIsInstance<VocabularyFilter>()
        val stageMapping = loadStageMapping().first()
        val newMappings = mutableListOf<CategoryMapping>()

        for (item in vocabularyItems) {
            for (filter in autoFilters) {
                if (isItemFitForCategory(item, filter, stageMapping)) {
                    newMappings.add(CategoryMapping(item.id, filter.id))
                }
            }
        }
        Log.d(TAG, "calculateFilterMappings: Generated ${newMappings.size} filter mappings.")
        return newMappings
    }


    suspend fun actualizeVocabularyStageMappings() {
        val allItems = getAllVocabularyItems()
        val currentStageMapping = loadStageMapping().first()
        val newStageMappings = allItems.associate { item ->
            item.id to (currentStageMapping[item.id] ?: VocabularyStage.NEW)
        }
        val allIds = allItems.map { it.id }
        mappingDao.deleteStageMappingsNotIn(allIds)
        saveStageMapping(newStageMappings)
        calculateCategoryProgress()
    }

    suspend fun getDueTodayItems(): List<VocabularyItem> {
        return getDueTodayItemsFlow().first()
    }

    suspend fun calculateStageStatistics(): List<StageStats> {
        val stageMapping = loadStageMapping().first()
        val counts = stageMapping.values.groupingBy { it }.eachCount()
        return VocabularyStage.entries.map { stage ->
            StageStats(stage, counts[stage] ?: 0)
        }
    }

    suspend fun getDailyCorrectCount(date: LocalDate): Int {
        return dailyStatDao.getStatForDate(date)?.correctCount ?: 0
    }

    suspend fun updateDailyCorrectCount(date: LocalDate, count: Int) {
        dailyStatDao.upsertStat(DailyStatEntity(date, count))
    }

    suspend fun getCorrectCountsForLastDays(days: Int): Map<LocalDate, Int> {
        val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
        val counts = mutableMapOf<LocalDate, Int>()
        for (i in 0 until days) {
            val date = today.minus(i, DateTimeUnit.DAY)
            counts[date] = getDailyCorrectCount(date)
        }
        return counts
    }

    suspend fun isTargetMetForDate(date: LocalDate): Boolean {
        val dailyCorrectCount = getDailyCorrectCount(date)
        val target = 10 // TODO: Replace with settingsRepository.dailyGoal.flow.first()
        return dailyCorrectCount >= target
    }

    suspend fun getAllLanguageIdsFromVocabulary(): Set<Int?> {
        Log.d(TAG, "getAllLanguageIdsFromVocabulary: Fetching all language IDs.")
        val languageIds = getAllVocabularyItems()
            .asSequence()
            .flatMap { sequenceOf(it.languageFirstId, it.languageSecondId) }
            .toSet()
        Log.d(TAG, "getAllLanguageIdsFromVocabulary: Found ${languageIds.size} unique language IDs: $languageIds")
        return languageIds
    }

    suspend fun calculateCategoryProgress(): List<CategoryProgress> = coroutineScope {
        val stageMappingDeferred = async { loadStageMapping().first() }
        val itemsDeferred = async { getAllVocabularyItems() }
        val mappingsDeferred = async { getCategoryMappings() }

        val categories = getAllCategories()
        val stageMapping = stageMappingDeferred.await()
        val allItems = itemsDeferred.await()
        val allMappings = mappingsDeferred.await()

        // Create maps for efficient lookups
        val itemsById = allItems.associateBy { it.id }
        val itemIdsByCategoryId = allMappings.groupBy(
            keySelector = { it.categoryId },
            valueTransform = { it.vocabularyItemId }
        )

        categories.map { category ->
            val itemIdsForCategory = itemIdsByCategoryId[category.id] ?: emptyList()
            val itemsInCategory = itemIdsForCategory.mapNotNull { itemsById[it] }

            val totalItems = itemsInCategory.size
            val itemsCompleted = itemsInCategory.count { stageMapping[it.id] == VocabularyStage.LEARNED }
            val itemsInStages = itemsInCategory.count {
                val stage = stageMapping[it.id]
                stage != VocabularyStage.NEW && stage != VocabularyStage.LEARNED
            }
            val newItems = itemsInCategory.count { stageMapping[it.id] == VocabularyStage.NEW || stageMapping[it.id] == null }

            CategoryProgress(
                vocabularyCategory = category,
                totalItems = totalItems,
                itemsCompleted = itemsCompleted,
                itemsInStages = itemsInStages,
                newItems = newItems,
            )
        }
    }

    suspend fun getSynonymsForItem(itemId: Int): List<VocabularyItem> {
        val item = getVocabularyItemById(itemId) ?: return emptyList()
        val lang1 = item.languageFirstId
        val lang2 = item.languageSecondId
        return if (lang1 != null && lang2 != null) {
            itemDao.getSynonyms(
                excludeId = item.id,
                lang1 = lang1,
                lang2 = lang2,
                wordFirst = item.wordFirst,
                wordSecond = item.wordSecond
            )
        } else {
            // Fallback: if we don't have both language IDs, do a minimal in-memory filter
            getAllVocabularyItems().asSequence()
                .filter { it.id != item.id }
                .filter { it.wordFirst == item.wordFirst || it.wordSecond == item.wordSecond }
                .toList()
        }
    }

    suspend fun getNewlyAddedCountForDate(date: LocalDate): Int {
        return getAllVocabularyItems().count { item ->
            item.createdAt?.toLocalDateTime(TimeZone.currentSystemDefault())?.date == date
        }
    }

    suspend fun getCompletedCountForDate(date: LocalDate): Int {
        val criteria = settingsRepository.criteriaCorrect.flow.first()
        return getAllVocabularyItemStates().count { state ->
            val completedDate = state.lastCorrectAnswer?.toLocalDateTime(TimeZone.currentSystemDefault())?.date
            completedDate == date && state.correctAnswerCount >= criteria
        }
    }

    suspend fun getCorrectAnswerCountForDate(date: LocalDate): Int {
        return getDailyCorrectCount(date)
    }

    @Suppress("unused")
    suspend fun backupRepositoryState(): String {
        Log.i(TAG, "backupRepositoryState: Creating repository backup string.")
        val backupData = RepositoryBackup(
            items = getAllVocabularyItems(),
            categories = getAllCategories(),
            states = getAllVocabularyItemStates(),
            categoryMappings = getCategoryMappings(),
            stageMappings = loadStageMapping().first().toList()
        )
        return Json.encodeToString(RepositoryBackup.serializer(), backupData)
    }

    @Suppress("unused")
    suspend fun restoreRepositoryState(backupJson: String) {
        Log.w(TAG, "restoreRepositoryState: Restoring repository from backup. This will replace all existing data.")
        val backupData = Json.decodeFromString(RepositoryBackup.serializer(), backupJson)

        db.withTransaction {
            itemDao.insertAll(backupData.items)
            stateDao.insertAll(backupData.states)
            val categoryEntities = backupData.categories.map { mapCategoryToEntity(it) }
            categoryDao.insertAll(categoryEntities)
            val categoryMappingEntities = backupData.categoryMappings.map { CategoryMappingEntity(it.vocabularyItemId, it.categoryId) }
            mappingDao.setAllCategoryMappings(categoryMappingEntities)
            val stageMappingEntities = backupData.stageMappings.map { StageMappingEntity(it.first, it.second) }
            mappingDao.upsertStageMappings(stageMappingEntities)
        }
        requestUpdateMappings()
        Log.i(TAG, "restoreRepositoryState: Restore complete.")
    }

    suspend fun wipeRepository() {
        Log.e(TAG, "wipeRepository: Deleting all repository data!")
        db.withTransaction {
            mappingDao.clearCategoryMappings()
            mappingDao.clearStageMappings()
            categoryDao.clearAllCategories()
            stateDao.clearAllStates()
            itemDao.clearAllItems()
            dailyStatDao.clearAll()
        }
        requestUpdateMappings()
        Log.i(TAG, "wipeRepository: All data deleted.")
    }

    /**
     * Prints a detailed summary of the current repository state to the debug log.
     * This function is intended for debugging purposes.
     */
    @Suppress("unused")
    suspend fun printRepoState() {
        val allItems = getAllVocabularyItems()
        val allCategories = getAllCategories()
        val allStates = getAllVocabularyItemStates()
        val categoryMappings = getCategoryMappings()
        val stageMapping = loadStageMapping().first()

        val itemsPerCategory = categoryMappings.groupBy { it.categoryId }
            .mapValues { it.value.size }

        val itemsPerStage = stageMapping.values.groupingBy { it }.eachCount()

        Log.d(TAG, "--- REPOSITORY STATE ---")
        Log.d(TAG, "Total Items: ${allItems.size}")
        Log.d(TAG, "Total Categories: ${allCategories.size} (${allCategories.filterIsInstance<TagCategory>().size} Tags, ${allCategories.filterIsInstance<VocabularyFilter>().size} Filters)")
        Log.d(TAG, "Total Item States: ${allStates.size}")
        Log.d(TAG, "Total Category Mappings: ${categoryMappings.size}")
        Log.d(TAG, "Total Stage Mappings: ${stageMapping.size}")
        Log.d(TAG, "--- Items per Category ---")
        allCategories.forEach { category ->
            Log.d(TAG, "  - ${category.name} (ID: ${category.id}, Type: ${category::class.simpleName}): ${itemsPerCategory[category.id] ?: 0} items")
        }
        Log.d(TAG, "--- Items per Stage ---")
        VocabularyStage.entries.forEach { stage ->
            Log.d(TAG, "  - ${stage.name}: ${itemsPerStage[stage] ?: 0} items")
        }
        Log.d(TAG, "--- END REPOSITORY STATE ---")
    }
}

@Serializable
data class RepositoryBackup(
    val items: List<VocabularyItem>,
    val categories: List<VocabularyCategory>,
    val states: List<VocabularyItemState>,
    val categoryMappings: List<CategoryMapping>,
    val stageMappings: List<Pair<Int, VocabularyStage>>
)