package eu.gaudian.translator.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.model.repository.SettingsRepository
import eu.gaudian.translator.model.repository.VocabularyRepository
import eu.gaudian.translator.utils.Log
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.minus
import kotlinx.datetime.toLocalDateTime
import java.text.DateFormatSymbols
import java.util.Locale
import javax.inject.Inject


data class CategoryProgress(
    val vocabularyCategory: VocabularyCategory,
    val totalItems: Int,
    val newItems: Int,
    val itemsInStages: Int,
    val itemsCompleted: Int,
)

data class DayStreak(
    val day: String,
    val targetMet: Boolean,
)

data class WeeklyActivityStat(
    val day: String,
    val newlyAdded: Int,
    val completed: Int,
    val answeredRight: Int
)

data class StageStats(
    val stage: VocabularyStage,
    val itemCount: Int
)

@Suppress("SameParameterValue")
@HiltViewModel
class ProgressViewModel @Inject constructor(
    application: Application,
    private val vocabularyRepository: VocabularyRepository,
    private val settingsRepository: SettingsRepository
) : AndroidViewModel(application) {

    // Single-flight + coalescing scheduler for refresh()
    private val refreshMutex = Mutex()
    @Volatile private var isRefreshing = false
    @Volatile private var pendingRefresh = false
    private var refreshDebounceJob: Job? = null
    private val refreshDebounceMs = 250L

    private val _categoryProgressList = MutableStateFlow<List<CategoryProgress>>(emptyList())
    val categoryProgressList: StateFlow<List<CategoryProgress>> = _categoryProgressList.asStateFlow()

    private val _lastSevenDays = MutableStateFlow<List<DayStreak>>(emptyList())
    val lastSevenDays: StateFlow<List<DayStreak>> = _lastSevenDays.asStateFlow()

    private val _streak = MutableStateFlow(0)
    val streak: StateFlow<Int> = _streak.asStateFlow()

    private val _selectedCategories = MutableStateFlow<Set<Int>>(emptySet())
    val selectedCategories: StateFlow<Set<Int>> = _selectedCategories.asStateFlow()

    private val _dueTodayCount = MutableStateFlow(0)
    val dueTodayCount: StateFlow<Int> = _dueTodayCount.asStateFlow()

    private val _totalWordsCompleted = MutableStateFlow(0)
    val totalWordsCompleted: StateFlow<Int> = _totalWordsCompleted.asStateFlow()

    private val _totalWordsInProgress = MutableStateFlow(0)
    val totalWordsInProgress: StateFlow<Int> = _totalWordsInProgress.asStateFlow()

    private val _totalWords = MutableStateFlow(0)
    val totalWords: StateFlow<Int> = _totalWords.asStateFlow()

    private val _weeklyActivityStats = MutableStateFlow<List<WeeklyActivityStat>>(emptyList())
    val weeklyActivityStats: StateFlow<List<WeeklyActivityStat>> = _weeklyActivityStats.asStateFlow()

    private val _dailyVocabularyStats = MutableStateFlow<Map<LocalDate, Int>>(emptyMap())
    val dailyVocabularyStats: StateFlow<Map<LocalDate, Int>> = _dailyVocabularyStats.asStateFlow()

    private val _dailyGoal = MutableStateFlow(10)
    val dailyGoal: StateFlow<Int> = _dailyGoal.asStateFlow()

    private val _todayCompletedCount = MutableStateFlow(0)
    val todayCompletedCount: StateFlow<Int> = _todayCompletedCount.asStateFlow()

    init {
        viewModelScope.launch {
            // Delay the first refresh a bit to avoid colliding with repository init
            delay(1500L)
            tickerFlowInternal(5000L).collect {
                // Coalesced refresh requests
                requestRefresh()
            }
        }
        viewModelScope.launch {
            vocabularyRepository.getDueTodayItemsFlow().collect {
                _dueTodayCount.value = it.size
            }
        }
    }

    private fun tickerFlowInternal(period: Long) = flow {
        while (true) {
            emit(Unit)
            delay(period)
        }
    }


    suspend fun getLastSevenDays(): List<DayStreak> {
        val dailyCounts = vocabularyRepository.getCorrectCountsForLastDays(7)

        return dailyCounts.entries
            .sortedByDescending { it.key }
            .map { (date, _) ->
                // Map ISO day (Mon=1...Sun=7) to Calendar day (Sun=1...Sat=7) for DateFormatSymbols
                // kotlinx.datetime.DayOfWeek is an enum (Mon=0...Sun=6).
                // Logic: ((ordinal + 1) % 7) + 1  ->  Mon(0)=>2, Sun(6)=>1
                val calendarDay = ((date.dayOfWeek.ordinal + 1) % 7) + 1

                // Get localized short name (e.g., "Mon", "Seg") from the device's default locale
                val localizedDay = DateFormatSymbols.getInstance(Locale.getDefault()).shortWeekdays[calendarDay].uppercase()

                DayStreak(localizedDay, vocabularyRepository.isTargetMetForDate(date))
            }
    }

    suspend fun calculateDailyStreak(): Int {
        val dailyData = vocabularyRepository.getCorrectCountsForLastDays(365)
        val today = kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
        var currentStreak = 0

        for (i in 0..365) {
            val dateToCheck = today.minus(i, DateTimeUnit.DAY)
            if (dailyData.containsKey(dateToCheck) && vocabularyRepository.isTargetMetForDate(dateToCheck)) {
                currentStreak++
            } else {
                break
            }
        }
        return currentStreak
    }

    fun toggleCategorySelection(category: Int) {
        val currentSelection = _selectedCategories.value.toMutableSet()
        if (currentSelection.contains(category)) {
            currentSelection.remove(category)
        } else {
            currentSelection.add(category)
        }
        _selectedCategories.value = currentSelection
        saveSelectedCategories(currentSelection)
    }

    private fun loadSelectedCategories() {
        viewModelScope.launch {
            val stringSet = settingsRepository.selectedCategories.flow.first()
            _selectedCategories.value = stringSet.mapNotNull { it.toIntOrNull() }.toSet()
        }
    }

    private fun saveSelectedCategories(categories: Set<Int>) {
        viewModelScope.launch {
            settingsRepository.selectedCategories.set(categories.map { it.toString() }.toSet())
        }
    }

    suspend fun getWeeklyActivityStats(): List<WeeklyActivityStat> {
        val today = kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date

        // 1. Create a list of the last 7 dates in chronological order.
        val lastSevenDates = (0..6).map { i ->
            today.minus(6 - i, DateTimeUnit.DAY)
        }

        // 2. Map each date to its corresponding statistics.
        return lastSevenDates.map { date ->
            val newlyAdded = vocabularyRepository.getNewlyAddedCountForDate(date)
            val completed = vocabularyRepository.getCompletedCountForDate(date)
            val answeredRight = vocabularyRepository.getCorrectAnswerCountForDate(date)

            // Calculate localized day name
            val calendarDay = ((date.dayOfWeek.ordinal + 1) % 7) + 1
            val localizedDay = DateFormatSymbols.getInstance(Locale.getDefault()).shortWeekdays[calendarDay].uppercase()

            WeeklyActivityStat(
                // 3. Get the actual day name from the date and take the first 3 letters.
                day = localizedDay,
                newlyAdded = newlyAdded,
                completed = completed,
                answeredRight = answeredRight
            )
        }
    }

    suspend fun getDailyVocabularyStats(): Map<LocalDate, Int> {
        val today = kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
        val startDate = today.minus(365, DateTimeUnit.DAY) // Get up to a year of data

        return vocabularyRepository.getDailyVocabularyStats(startDate, today)
    }


    //let's keep as public entry point
    fun refresh() {
        requestRefresh()
    }

    private fun requestRefresh() {
        pendingRefresh = true
        refreshDebounceJob?.cancel()
        refreshDebounceJob = viewModelScope.launch {
            delay(refreshDebounceMs)
            triggerRefreshIfNeeded()
        }
    }

    private fun triggerRefreshIfNeeded() {
        if (isRefreshing) return
        if (!pendingRefresh) return
        pendingRefresh = false
        isRefreshing = true
        viewModelScope.launch {
            try {
                runRefreshInternal()
            } finally {
                isRefreshing = false
                if (pendingRefresh) {
                    refreshDebounceJob?.cancel()
                    refreshDebounceJob = viewModelScope.launch {
                        delay(refreshDebounceMs)
                        triggerRefreshIfNeeded()
                    }
                }
            }
        }
    }

    private suspend fun runRefreshInternal() {
        refreshMutex.lock()
        try {
            loadSelectedCategories()
            try {
                // Load daily goal setting
                val dailyGoalValue = settingsRepository.dailyGoal.flow.first()
                _dailyGoal.value = dailyGoalValue

                // Get today's completed count
                val today = kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
                val todayCompleted = vocabularyRepository.getCorrectAnswerCountForDate(today)
                _todayCompletedCount.value = todayCompleted

                val progressDeferred = viewModelScope.async { vocabularyRepository.calculateCategoryProgress() }
                val lastSevenDaysDeferred = viewModelScope.async { getLastSevenDays() }
                val streakDeferred = viewModelScope.async { calculateDailyStreak() }
                val weeklyStatsDeferred = viewModelScope.async { getWeeklyActivityStats() }
                val dailyStatsDeferred = viewModelScope.async { getDailyVocabularyStats() }

                val progressList = progressDeferred.await()
                val stageList = vocabularyRepository.calculateStageStatistics()
                _categoryProgressList.value = progressList
                _totalWordsCompleted.value = stageList.firstOrNull { it.stage == VocabularyStage.LEARNED }?.itemCount ?: 0

                _totalWordsInProgress.value = stageList
                    .filter { it.stage != VocabularyStage.LEARNED && it.stage != VocabularyStage.NEW }
                    .sumOf { it.itemCount }

                _totalWords.value = stageList.sumOf { it.itemCount }

                if (_selectedCategories.value.isEmpty() && progressList.isNotEmpty()) {
                    val initialCategory = setOf(progressList.first().vocabularyCategory.id)
                    _selectedCategories.value = initialCategory
                    saveSelectedCategories(initialCategory)
                }

                _lastSevenDays.value = lastSevenDaysDeferred.await()
                _streak.value = streakDeferred.await()
                _weeklyActivityStats.value = weeklyStatsDeferred.await()
                _dailyVocabularyStats.value = dailyStatsDeferred.await()
            } catch (e: Exception) {
                @Suppress("HardCodedStringLiteral")
                Log.e("ProgressViewModel", "Error during refresh: ${e.message}", e)
            }
        } finally {
            refreshMutex.unlock()
        }
    }

}
