@file:Suppress("HardCodedStringLiteral")

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.VocabularyItem
import eu.gaudian.translator.model.repository.LanguageConfigRepository
import eu.gaudian.translator.model.repository.LanguageRepository
import eu.gaudian.translator.model.repository.VocabularyRepository
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.StringHelper
import eu.gaudian.translator.view.vocabulary.VocabularyExerciseAction
import eu.gaudian.translator.view.vocabulary.VocabularyExerciseState
import eu.gaudian.translator.view.vocabulary.VocabularyExerciseType
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import javax.inject.Inject
import kotlin.random.Random
import kotlin.time.Clock
import kotlin.time.ExperimentalTime

enum class ScreenState {
    START,
    EXERCISE,
    RESULT
}

data class ExerciseConfig(
    val shuffleCards: Boolean = true,
    val shuffleLanguages: Boolean = true,
    val trainingMode: Boolean = false,
    val dueTodayOnly: Boolean = false,
    val selectedExerciseTypes: Set<VocabularyExerciseType> = setOf(VocabularyExerciseType.GUESSING),
    val exerciseItemCount: Int = 0,
    val originalExerciseItems: List<VocabularyItem> = emptyList(),
    val originLanguageId: Int? = null,
    val targetLanguageId: Int? = null
)

data class ExerciseResults(
    val finalScore: Int = 0,
    val finalWrongAnswers: Int = 0
)

/**
 * VocabularyExerciseViewModel - Hilt-enabled ViewModel for vocabulary exercises
 *
 * This ViewModel manages vocabulary exercise sessions including different exercise types,
 * tracking progress, and managing exercise state across configuration changes.
 */
@HiltViewModel
class VocabularyExerciseViewModel @Inject constructor(
    application: Application,
    private val vocabularyRepository: VocabularyRepository,
    private val languageConfigRepository: LanguageConfigRepository,
    private val languageRepository: LanguageRepository
) : AndroidViewModel(application) {

    private val _exerciseState = MutableStateFlow<VocabularyExerciseState?>(null)
    val exerciseState: StateFlow<VocabularyExerciseState?> = _exerciseState.asStateFlow()

    // Added StateFlows for tracking correct and wrong answers
    private val _correctAnswers = MutableStateFlow(0)
    val correctAnswers: StateFlow<Int> = _correctAnswers.asStateFlow()

    private val _wrongAnswers = MutableStateFlow(0)
    val wrongAnswers: StateFlow<Int> = _wrongAnswers.asStateFlow()

    // Track which items were answered wrong for retry functionality
    private val _wrongItems = MutableStateFlow<Set<Int>>(emptySet())

    private var items: List<VocabularyItem> = emptyList()
    // Working list for this exercise session to allow safe in-place swaps without duplication
    private var currentItems: MutableList<VocabularyItem> = mutableListOf()
    private var currentIndex = 0
    private var exerciseTypes: Set<VocabularyExerciseType> = setOf(VocabularyExerciseType.GUESSING)
    private var shuffleLanguages = false

    private val _trainingMode = MutableStateFlow(false)
    val trainingMode: StateFlow<Boolean> = _trainingMode.asStateFlow()

    // Screen state management to survive configuration changes
    private val _screenState = MutableStateFlow(ScreenState.START)
    val screenState: StateFlow<ScreenState> = _screenState.asStateFlow()

    // Exercise configuration state
    private val _exerciseConfig = MutableStateFlow(ExerciseConfig())
    private val _pendingExerciseConfig = MutableStateFlow(ExerciseConfig())

    val pendingExerciseConfig: StateFlow<ExerciseConfig> = _pendingExerciseConfig.asStateFlow()

    // Exercise results state
    private val _exerciseResults = MutableStateFlow(ExerciseResults())

    // State to persist total items and original items across configuration changes
    private val _totalItems = MutableStateFlow(0)
    val totalItems: StateFlow<Int> = _totalItems.asStateFlow()

    private val _originalItems = MutableStateFlow<List<VocabularyItem>>(emptyList())
    val originalItems: StateFlow<List<VocabularyItem>> = _originalItems.asStateFlow()

    fun startExercise(
        items: List<VocabularyItem>,
        types: Set<VocabularyExerciseType>,
        shuffleLanguages: Boolean
    ) {
        Log.d("ExerciseVM", "startExercise called: items=${items.size}, types=$types, shuffleLanguages=$shuffleLanguages")
        // Reset counters for the new exercise session
        _correctAnswers.value = 0
        _wrongAnswers.value = 0
        _wrongItems.value = emptySet()

        this.items = items
        this.currentItems = items.shuffled().toMutableList() // Shuffle working list at the start of the exercise
        this.currentIndex = 0
        this.exerciseTypes = types.ifEmpty { setOf(VocabularyExerciseType.GUESSING) }
        this.shuffleLanguages = shuffleLanguages
        loadExercise()
    }

    // Start exercise with only the wrong items from previous session
    fun startRetryExercise(
        originalItems: List<VocabularyItem>,
        types: Set<VocabularyExerciseType>,
        shuffleLanguages: Boolean
    ) {
        // Capture wrong items before resetting counters
        val wrongItemIds = _wrongItems.value
        val retryItems = originalItems.filter { it.id in wrongItemIds }

        // Reset counters for the new exercise session
        _correctAnswers.value = 0
        _wrongAnswers.value = 0
        _wrongItems.value = emptySet()

        this.items = retryItems
        this.currentItems = retryItems.shuffled().toMutableList()
        this.currentIndex = 0
        this.exerciseTypes = types.ifEmpty { setOf(VocabularyExerciseType.GUESSING) }
        this.shuffleLanguages = shuffleLanguages
        loadExercise()
    }

    fun onAction(action: VocabularyExerciseAction) {
        viewModelScope.launch {
            when (action) {
                is VocabularyExerciseAction.Reveal -> revealAnswer()
                is VocabularyExerciseAction.Submit -> checkAnswer(action.answer)
                is VocabularyExerciseAction.Next -> nextItem()
                is VocabularyExerciseAction.UpdateWordJumble -> updateJumbledWord(action.assembledWord)
            }
        }
    }

    private fun isSentenceItem(item: VocabularyItem): Boolean {
        return StringHelper.isSentenceLoose(item.wordFirst) || StringHelper.isSentenceLoose(item.wordSecond)
    }

    private fun loadExercise() {
        Log.d("ExerciseVM", "loadExercise: index=$currentIndex, total=${currentItems.size}")
        if (currentIndex < currentItems.size) {
            // Ensure item categories align with exercise type by attempting a swap instead of replacement
            val randomType = exerciseTypes.random()
            val config = _exerciseConfig.value

            // Try to swap based on desired categories
            val currentIsSentence = isSentenceItem(currentItems[currentIndex])
            when (randomType) {
                VocabularyExerciseType.WORD_JUMBLE, VocabularyExerciseType.SPELLING -> {
                    // Prefer non-sentence. If current is sentence, try to find a later non-sentence to swap with.
                    if (currentIsSentence) {
                        val swapIndex = (currentIndex + 1 until currentItems.size).firstOrNull { !isSentenceItem(currentItems[it]) }
                        if (swapIndex != null) {
                            val tmp = currentItems[currentIndex]
                            currentItems[currentIndex] = currentItems[swapIndex]
                            currentItems[swapIndex] = tmp
                        }
                    }
                }
                VocabularyExerciseType.GUESSING, VocabularyExerciseType.MULTIPLE_CHOICE -> {
                    // Prefer sentence. If current is non-sentence, try to find a later sentence to swap with.
                    if (!currentIsSentence) {
                        val swapIndex = (currentIndex + 1 until currentItems.size).firstOrNull { isSentenceItem(currentItems[it]) }
                        if (swapIndex != null) {
                            val tmp = currentItems[currentIndex]
                            currentItems[currentIndex] = currentItems[swapIndex]
                            currentItems[swapIndex] = tmp
                        }
                    }
                }
            }

            val itemToUse = currentItems[currentIndex]
            
            // Determine language switching based on selected origin and target languages
            val isSwitched = when {
                // If specific languages are selected, use them to determine switching
                config.originLanguageId != null || config.targetLanguageId != null -> {
                    val originMatchesFirst = config.originLanguageId == null || config.originLanguageId == itemToUse.languageFirstId
                    val targetMatchesFirst = config.targetLanguageId == null || config.targetLanguageId == itemToUse.languageFirstId
                    val originMatchesSecond = config.originLanguageId == null || config.originLanguageId == itemToUse.languageSecondId
                    val targetMatchesSecond = config.targetLanguageId == null || config.targetLanguageId == itemToUse.languageSecondId
                    
                    val itemMatchesForward = originMatchesFirst && targetMatchesSecond
                    val itemMatchesReversed = originMatchesSecond && targetMatchesFirst

                    // If the item doesn't match the selected language pair (even partially), don't use it
                    if (!(itemMatchesForward || itemMatchesReversed)) {
                        // Try to find a matching item
                        val matchingIndex = (currentIndex + 1 until currentItems.size).firstOrNull { index ->
                            val item = currentItems[index] // Get the VocabularyItem using the index
                            val itemOriginMatchesFirst = config.originLanguageId == null || config.originLanguageId == item.languageFirstId
                            val itemTargetMatchesFirst = config.targetLanguageId == null || config.targetLanguageId == item.languageFirstId
                            val itemOriginMatchesSecond = config.originLanguageId == null || config.originLanguageId == item.languageSecondId
                            val itemTargetMatchesSecond = config.targetLanguageId == null || config.targetLanguageId == item.languageSecondId
                            (itemOriginMatchesFirst && itemTargetMatchesSecond) || (itemOriginMatchesSecond && itemTargetMatchesFirst)
                        }

                        if (matchingIndex != null) {
                            val tmp = currentItems[currentIndex]
                            currentItems[currentIndex] = currentItems[matchingIndex]
                            currentItems[matchingIndex] = tmp

                            // Check if we need to switch the new item
                            val newItem = currentItems[currentIndex]
                            // Switch if origin is specified and it's the second language
                            config.originLanguageId != null && config.originLanguageId == newItem.languageSecondId
                        } else {
                            // No matching item found, fall back to random behavior
                            if (shuffleLanguages) Random.nextBoolean() else (config.originLanguageId != null && config.originLanguageId == itemToUse.languageSecondId)
                        }
                    } else {
                        // Item matches, determine if we need to switch
                        //  if origin is specified, and it's not the first language, or if target is specified and it IS the first language
                        (config.originLanguageId != null && config.originLanguageId != itemToUse.languageFirstId) ||
                                (config.originLanguageId == null && config.targetLanguageId != null && config.targetLanguageId == itemToUse.languageFirstId)
                    }
                }
                // Fallback to original random behavior
                else -> if (shuffleLanguages) Random.nextBoolean() else false
            }

            @Suppress("HardCodedStringLiteral")
            Log.d("ExerciseDebug", "Item: ${itemToUse.wordFirst} (${itemToUse.languageFirstId}) / ${itemToUse.wordSecond} (${itemToUse.languageSecondId}), Switched: $isSwitched")
            @Suppress("HardCodedStringLiteral")
            Log.d("ExerciseDebug", "Origin Lang: ${config.originLanguageId}, Target Lang: ${config.targetLanguageId}")

            // Set the exercise state based on the random type
            _exerciseState.value = when (randomType) {
                VocabularyExerciseType.GUESSING -> VocabularyExerciseState.Guessing(
                    item = itemToUse,
                    isSwitched = isSwitched
                )
                VocabularyExerciseType.SPELLING -> VocabularyExerciseState.Spelling(
                    item = itemToUse,
                    isSwitched = isSwitched
                )
                VocabularyExerciseType.MULTIPLE_CHOICE -> {
                    val correctAnswer = if (isSwitched) itemToUse.wordFirst else itemToUse.wordSecond
                    val options = generateMultipleChoiceOptions(correctAnswer, isSwitched)
                    VocabularyExerciseState.MultipleChoice(
                        item = itemToUse,
                        isSwitched = isSwitched,
                        options = options
                    )
                }
                VocabularyExerciseType.WORD_JUMBLE -> {
                    val wordToJumble = if (isSwitched) itemToUse.wordFirst else itemToUse.wordSecond
                    VocabularyExerciseState.WordJumble(
                        item = itemToUse,
                        isSwitched = isSwitched,
                        jumbledLetters = wordToJumble.toList().mapIndexed { index, char -> Pair(char, index) }.shuffled()
                    )
                }
            }
            Log.d("ExerciseVM", "exerciseState set: type=$randomType, itemId=${itemToUse.id}")
        } else {
            _exerciseState.value = null // End of exercise
            Log.d("ExerciseVM", "loadExercise: end of exercise, state cleared")
        }
    }

    private fun generateMultipleChoiceOptions(correctAnswer: String, isSwitched: Boolean): List<String> {
        val options = mutableSetOf(correctAnswer)
        val allWords = currentItems.map { if (isSwitched) it.wordFirst else it.wordSecond }

        val sameStartLetterWords = allWords.filter { it.firstOrNull() == correctAnswer.firstOrNull() && it != correctAnswer }
        options.addAll(sameStartLetterWords.take(3))

        if (options.size < 4) {
            val sameLengthWords = allWords.filter { it.length == correctAnswer.length && it != correctAnswer && !options.contains(it) }
            options.addAll(sameLengthWords.take(4 - options.size))
        }

        if (options.size < 4) {
            val vowelCount = correctAnswer.count { "aeiouAEIOU".contains(it) }
            val similarVowelWords = allWords.filter { it.count { v -> "aeiouAEIOU".contains(v) } == vowelCount && it != correctAnswer && !options.contains(it) }
            options.addAll(similarVowelWords.take(4 - options.size))
        }

        if (options.size < 4) {
            val distractors = allWords
                .filter { it != correctAnswer && !options.contains(it) }
                .shuffled()
                .take(4 - options.size)
            options.addAll(distractors)
        }


        return options.shuffled().take(4)
    }

    private fun revealAnswer() {
        _exerciseState.value = when (val state = _exerciseState.value) {
            is VocabularyExerciseState.Guessing -> state.copy(isRevealed = true)
            is VocabularyExerciseState.Spelling -> state.copy(isRevealed = true)
            is VocabularyExerciseState.MultipleChoice -> state.copy(isRevealed = true)
            is VocabularyExerciseState.WordJumble -> state.copy(isRevealed = true)
            null -> null
        }
    }

    private suspend fun checkAnswer(answer: Any) {
        val state = _exerciseState.value ?: return
        val correctAnswer = if (state.isSwitched) state.item.wordFirst else state.item.wordSecond

        val isCorrect = when (state) {
            is VocabularyExerciseState.Spelling -> {
                val userAnswer = (answer as String).trim()
                val languageId = if (state.isSwitched) state.item.languageFirstId else state.item.languageSecondId
                val language = languageRepository.getLanguageById(languageId ?: 0)
                    ?: return

                // Get articles for the language
                val articles = languageConfigRepository.getArticlesForLanguage(language.code)

                // Normalize and split possible answers using helper
                val normalizedCorrectAnswer = StringHelper.normalizeAnswer(correctAnswer)
                val possibleAnswers = StringHelper.possibleAnswersFromSlash(normalizedCorrectAnswer)

                val userNormalizedAnswer = StringHelper.normalizeAnswer(userAnswer)

                // Check if the user's answer matches any of the possible correct answers
                val isDirectMatch = possibleAnswers.any {
                    it.equals(userNormalizedAnswer, ignoreCase = true)
                }

                // Check if user answer with a removed article matches any possible correct answer
                val isMatchWithArticleRemoved = if (articles.isNotEmpty()) {
                    val userWithoutArticle = StringHelper.removeLeadingArticlesOneOf(userNormalizedAnswer, articles)
                    possibleAnswers.any { it.equals(userWithoutArticle, ignoreCase = true) }
                } else {
                    false
                }

                //TODO val hasGuessedSynonym = vocabularyRepository.getSynonymsForItem(state.item.id)

                isDirectMatch || isMatchWithArticleRemoved
            }
            is VocabularyExerciseState.Guessing -> answer as Boolean
            is VocabularyExerciseState.MultipleChoice -> (answer as String).equals(correctAnswer, ignoreCase = true)
            is VocabularyExerciseState.WordJumble -> (answer as String).equals(correctAnswer, ignoreCase = true)
        }

        if (isCorrect) {
            _correctAnswers.value++
        } else {
            _wrongAnswers.value++
            // Track the wrong item for retry functionality
            _wrongItems.value += state.item.id
        }

        updateVocabularyItemState(state.item.id, isCorrect)

        _exerciseState.value = when (state) {
            is VocabularyExerciseState.Guessing -> state.copy(isCorrect = isCorrect, isRevealed = true)
            is VocabularyExerciseState.Spelling -> state.copy(isCorrect = isCorrect, isRevealed = true, userAnswer = answer as String)
            is VocabularyExerciseState.MultipleChoice -> state.copy(isCorrect = isCorrect, isRevealed = true, selectedAnswer = answer as String)
            is VocabularyExerciseState.WordJumble -> state.copy(isCorrect = isCorrect, isRevealed = true)
        }
    }

    private fun updateJumbledWord(assembledWord: List<Pair<Char, Int>>) {
        _exerciseState.value = when (val state = _exerciseState.value) {
            is VocabularyExerciseState.WordJumble -> state.copy(assembledWord = assembledWord)
            else -> state
        }
    }

    private fun nextItem() {
        currentIndex++
        loadExercise()
    }

    fun startExerciseWithConfig(
        items: List<VocabularyItem>,
        config: ExerciseConfig
    ) {
        Log.d("ExerciseVM", "startExerciseWithConfig called: items=${items.size}, configCount=${config.exerciseItemCount}, shuffleCards=${config.shuffleCards}, shuffleLanguages=${config.shuffleLanguages}, trainingMode=${config.trainingMode}, dueTodayOnly=${config.dueTodayOnly}, types=${config.selectedExerciseTypes}")
        _exerciseConfig.value = config
        _pendingExerciseConfig.value = config
        _totalItems.value = items.size
        _originalItems.value = items
        startExercise(items, config.selectedExerciseTypes, config.shuffleLanguages)
        _screenState.value = ScreenState.EXERCISE
        Log.d("ExerciseVM", "screenState set to EXERCISE; totalItems=${_totalItems.value}")
    }

    fun updatePendingExerciseConfig(config: ExerciseConfig) {
        Log.d("ExerciseVM", "updatePendingExerciseConfig: count=${config.exerciseItemCount}, shuffleCards=${config.shuffleCards}, shuffleLanguages=${config.shuffleLanguages}, trainingMode=${config.trainingMode}, dueTodayOnly=${config.dueTodayOnly}, types=${config.selectedExerciseTypes}")
        _pendingExerciseConfig.value = config
    }


    fun finishExercise(score: Int, wrongAnswers: Int) {
        _exerciseResults.value = ExerciseResults(score, wrongAnswers)
        _screenState.value = ScreenState.RESULT
    }

    fun resetExercise() {
        Log.d("ExerciseVM", "resetExercise called")
        _screenState.value = ScreenState.START
        _exerciseConfig.value = ExerciseConfig()
        _exerciseResults.value = ExerciseResults()
        _correctAnswers.value = 0
        _wrongAnswers.value = 0
        _wrongItems.value = emptySet()
        _exerciseState.value = null
        _totalItems.value = 0
        _originalItems.value = emptyList()
        Log.d("ExerciseVM", "resetExercise completed; screenState=START")
    }

    fun retryWrongAnswers(originalItems: List<VocabularyItem>) {
        val wrongItemIds = _wrongItems.value
        val retryItems = originalItems.filter { it.id in wrongItemIds }

        if (retryItems.isNotEmpty()) {
            _totalItems.value = retryItems.size
            _originalItems.value = originalItems
            val config = _exerciseConfig.value.copy(
                exerciseItemCount = retryItems.size,
                originalExerciseItems = originalItems
            )
            _exerciseConfig.value = config
            startRetryExercise(originalItems, config.selectedExerciseTypes, config.shuffleLanguages)
            _screenState.value = ScreenState.EXERCISE
        }
    }

    @OptIn(ExperimentalTime::class)
    private fun updateVocabularyItemState(vocabularyItemId: Int, isCorrect: Boolean) {
        viewModelScope.launch {
            val item = vocabularyRepository.getVocabularyItemById(vocabularyItemId)
            item?.let {
                if (!trainingMode.value) {
                    vocabularyRepository.updateFlashcardStage(it, isCorrect)
                }
                if (isCorrect) {
                    val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
                    val currentCount = vocabularyRepository.getDailyCorrectCount(today)
                    vocabularyRepository.updateDailyCorrectCount(today, currentCount + 1)
                }
            }
        }
    }
}
