@file:Suppress("HardCodedStringLiteral")

package eu.gaudian.translator.viewmodel

import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.CategorizationQuestion
import eu.gaudian.translator.model.Exercise
import eu.gaudian.translator.model.FillInTheBlankQuestion
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.ListeningComprehensionQuestion
import eu.gaudian.translator.model.MatchingPairsQuestion
import eu.gaudian.translator.model.MultipleChoiceQuestion
import eu.gaudian.translator.model.Question
import eu.gaudian.translator.model.SubtitleLine
import eu.gaudian.translator.model.TrueFalseQuestion
import eu.gaudian.translator.model.VocabularyTestQuestion
import eu.gaudian.translator.model.WordOrderQuestion
import eu.gaudian.translator.model.repository.ExerciseRepository
import eu.gaudian.translator.model.repository.LanguageRepository
import eu.gaudian.translator.model.repository.VocabularyRepository
import eu.gaudian.translator.utils.ExerciseService
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.TranslationService
import eu.gaudian.translator.utils.YouTubeApiService
import eu.gaudian.translator.utils.YouTubeExerciseService
import kotlinx.coroutines.Job
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.update
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.reflect.KClass

sealed class AnswerResult {
    object UNCHECKED : AnswerResult()
    object CORRECT : AnswerResult()
    data class INCORRECT(val feedback: String) : AnswerResult()
}

data class ExerciseSessionState(
    val exercise: Exercise,
    val questions: List<Question>,
    val currentQuestionIndex: Int = 0,
    val correctAnswers: Int = 0,
    val wrongAnswers: Int = 0,
    val selectedAnswer: Any? = null,
    val answerResult: AnswerResult = AnswerResult.UNCHECKED
) {
    val currentQuestion: Question get() = questions[currentQuestionIndex]
    val isExerciseFinished: Boolean get() = currentQuestionIndex >= questions.size


    fun isAnswerSelected(): Boolean {
        return when (val question = currentQuestion) {
            is FillInTheBlankQuestion, is ListeningComprehensionQuestion, is VocabularyTestQuestion -> (selectedAnswer as? String)?.isNotBlank() == true
            is WordOrderQuestion -> (selectedAnswer as? List<*>)?.isNotEmpty() == true
            is MatchingPairsQuestion -> ((selectedAnswer as? Map<*, *>)?.size ?: 0) == question.pairs.size
            is CategorizationQuestion -> ((selectedAnswer as? Map<*, *>)?.size ?: 0) == question.items.size
            else -> selectedAnswer != null
        }
    }
}

sealed class AiGenerationState {
    object Idle : AiGenerationState()
    data class Generating(val statusMessage: String) : AiGenerationState()
    data class Success(val generatedExerciseTitle: String) : AiGenerationState()
    data class Error(val errorMessage: String) : AiGenerationState()
}

sealed class YouTubeExerciseState {
    object Idle : YouTubeExerciseState()
    object Loading : YouTubeExerciseState()
    data class Success(
        val videoId: String,
        val videoTitle: String,
        val subtitles: List<SubtitleLine>
    ) : YouTubeExerciseState()
    data class Error(val message: String) : YouTubeExerciseState()
}

@HiltViewModel
class ExerciseViewModel @Inject constructor(
    application: Application,
    private val exerciseRepository: ExerciseRepository,
    private val exerciseService: ExerciseService,
    private val vocabularyRepository: VocabularyRepository,
    private val languageRepository: LanguageRepository,
    private val translationService: TranslationService,
    private val youTubeApiService: YouTubeApiService,
    private val youTubeExerciseService: YouTubeExerciseService
) : AndroidViewModel(application) {
    data class YouTubeSessionParams(val url: String, val sourceLanguage: String?, val targetLanguage: String?)

    @SuppressLint("StaticFieldLeak")
    val context = application.applicationContext!!

    private fun normalizeText(input: String?): String {
        if (input.isNullOrBlank()) return ""
        @Suppress("HardCodedStringLiteral") val normalized = java.text.Normalizer.normalize(input, java.text.Normalizer.Form.NFD)
            .replace("\\p{InCombiningDiacriticalMarks}+".toRegex(), "")
        val noPunct = normalized.replace("\\p{Punct}".toRegex(), " ")
        return noPunct.trim().replace("\\s+".toRegex(), " ").lowercase()
    }

    private fun normalizeTokens(tokens: List<String>): List<String> = tokens.map { normalizeText(it) }

    private fun <K, V> normalizeMap(map: Map<K, V>): Map<String, String> = map.mapNotNull { (k, v) ->
        val nk = normalizeText(k?.toString())
        val nv = normalizeText(v?.toString())
        if (nk.isNotBlank() && nv.isNotBlank()) nk to nv else null
    }.toMap()

    private val _exercises = MutableStateFlow<List<Exercise>>(emptyList())
    val exercises: StateFlow<List<Exercise>> = _exercises.asStateFlow()

    private val _exerciseSessionState = MutableStateFlow<ExerciseSessionState?>(null)
    val exerciseSessionState: StateFlow<ExerciseSessionState?> = _exerciseSessionState.asStateFlow()

    private val _aiGenerationState = MutableStateFlow<AiGenerationState>(AiGenerationState.Idle)
    val aiGenerationState: StateFlow<AiGenerationState> = _aiGenerationState.asStateFlow()

    private val _youTubeSessionParams = MutableStateFlow<YouTubeSessionParams?>(null)
    val youTubeSessionParams: StateFlow<YouTubeSessionParams?> = _youTubeSessionParams.asStateFlow()

    private val _youTubeExerciseState = MutableStateFlow<YouTubeExerciseState>(YouTubeExerciseState.Idle)
    val youTubeExerciseState: StateFlow<YouTubeExerciseState> = _youTubeExerciseState.asStateFlow()

    private var currentlyFetchingVideoId: String? = null
    private var currentSubtitleTranslationJob: Job? = null

    init {
        loadExercisesFromRepository()
    }

    private fun loadExercisesFromRepository() {
        viewModelScope.launch {
            exerciseRepository.getAllExercisesFlow().collect { exercisesList ->
                _exercises.value = exercisesList
            }
        }
    }

    fun generateExerciseWithAi(
        category: String,
        questionTypes: List<KClass<out Question>>,
        difficulty: String,
        amount: Int,
        sourceLanguage: String? = null,
        targetLanguage: String? = null
    ) {
        Log.i("ExerciseViewModel", "generateExerciseWithAi: category=$category, types=${questionTypes.map { it.simpleName }}, difficulty=$difficulty, amount=$amount, source=$sourceLanguage, target=$targetLanguage")
        viewModelScope.launch {
            try {
                _aiGenerationState.value = AiGenerationState.Generating(getApplication<Application>().applicationContext.getString(R.string.text_contacting_ai))

                val result = exerciseService.generateExerciseWithQuestions(
                    category,
                    questionTypes,
                    difficulty,
                    amount,
                    sourceLanguage,
                    targetLanguage
                ) { msg ->
                    _aiGenerationState.value = AiGenerationState.Generating(msg)
                }

                if (result != null) {
                    val (exercise, questions, newVocab) = result
                    Log.i("ExerciseViewModel", "AI generated exercise '$exercise.title}' with ${questions.size} questions and ${newVocab.size} new vocab items")

                    var savedVocabIds: List<Int> = emptyList()
                    if (newVocab.isNotEmpty()) {
                        vocabularyRepository.introduceVocabularyItems(newVocab)
                        val allVocab = vocabularyRepository.getAllVocabularyItems()
                        savedVocabIds = allVocab.filter { v -> newVocab.any { it.isDuplicate(v) } }.map { it.id }
                    }

                    exerciseRepository.saveNewExerciseWithQuestions(
                        exercise.copy(associatedVocabularyIds = savedVocabIds),
                        questions
                    )
                    Log.d("ExerciseViewModel", "Saved AI-generated exercise '${exercise.title}' with ${questions.size} questions; associatedVocabularyIds=${savedVocabIds.size}")

                    _aiGenerationState.value = AiGenerationState.Success(
                        getApplication<Application>().applicationContext.getString(
                            R.string.exercise_2d_created,
                            exercise.title
                        ))
                } else {
                    _aiGenerationState.value = AiGenerationState.Error(context.getString(R.string.text_ai_failed_to_create_the_exercise))
                }

            } catch (e: Exception) {
                Log.e(context.getString(R.string.text_ai_generation_failed_with_an_exception), e)
                _aiGenerationState.value = AiGenerationState.Error(e.message ?: context.getString(R.string.text_an_unknown_error_occurred))
            } finally {
                delay(2500)
                _aiGenerationState.value = AiGenerationState.Idle
            }
        }
    }

    fun startExercise(exercise: Exercise) {
        viewModelScope.launch {
            val allQuestions = exerciseRepository.getAllQuestionsFlow().first()
            val questionsForExercise = allQuestions.filter { it.id in exercise.questions }

            if (questionsForExercise.size != exercise.questions.size) {
                Log.w(context.getString(R.string.text_mismatch_between_question_ids_in_exercise_and_questions_found_in_repository))
            }

            _exerciseSessionState.value = ExerciseSessionState(
                exercise = exercise,
                questions = questionsForExercise
            )
        }
    }

    fun selectAnswer(answer: Any) {
        _exerciseSessionState.update { it?.copy(selectedAnswer = answer) }
    }

    fun checkAnswer() {
        val currentState = _exerciseSessionState.value ?: return
        if (!currentState.isAnswerSelected()) return

        var isCorrect: Boolean
        var feedback = context.getString(R.string.text_that_s_not_quite_right)

        when (val question = currentState.currentQuestion) {
            is TrueFalseQuestion -> {
                isCorrect = question.correctAnswer == currentState.selectedAnswer as? Boolean
                if (!isCorrect) {
                    question.explanation.ifBlank { context.getString(R.string.text_the_correct_answer_is_2d) + " " +(question.correctAnswer) }
                        .also { feedback = it }
                }
            }
            is MultipleChoiceQuestion -> {
                isCorrect = question.correctAnswerIndex == currentState.selectedAnswer as? Int
                if (!isCorrect) feedback = context.getString(R.string.text_the_correct_answer_is_2d)+ question.options[question.correctAnswerIndex]
            }
            is FillInTheBlankQuestion -> {
                val expected = normalizeText(question.correctAnswer)
                val given = normalizeText(currentState.selectedAnswer as? String)
                isCorrect = expected == given
                if (!isCorrect) feedback = context.getString(R.string.text_the_correct_answer_is_2d) + " " + question.correctAnswer
            }
            is WordOrderQuestion -> {
                val expected = normalizeTokens(question.correctOrder)
                val given = normalizeTokens((currentState.selectedAnswer as? List<*>)?.map { it?.toString() ?: "" } ?: emptyList())
                isCorrect = expected == given
                if (!isCorrect) feedback = context.getString(
                    R.string.text_the_correct_order_is_2d,
                    question.correctOrder.joinToString(" ")
                )
            }
            is MatchingPairsQuestion -> {
                val selRaw = (currentState.selectedAnswer as? Map<*, *>) ?: emptyMap<Any?, Any?>()
                val sel = normalizeMap(selRaw)
                val target = normalizeMap(question.pairs)
                isCorrect = target == sel
                if (!isCorrect) {
                    val correctCount = sel.count { (k, v) -> target[k] == v }
                    feedback = context.getString(R.string.text_check_your_matches) + " (" + correctCount + "/" + target.size + ")"
                }
            }
            is ListeningComprehensionQuestion -> {
                val expected = normalizeText(question.name)
                val given = normalizeText(currentState.selectedAnswer as? String)
                isCorrect = expected == given
                if (!isCorrect) feedback =
                    context.getString(R.string.text_the_correct_sentence_was_2d, question.name)
            }
            is CategorizationQuestion -> {
                val selRaw = (currentState.selectedAnswer as? Map<*, *>) ?: emptyMap<Any?, Any?>()
                val sel = normalizeMap(selRaw)
                val target = normalizeMap(question.correctMapping)
                isCorrect = target == sel
                if (!isCorrect) {
                    val correctCount = sel.count { (item, cat) -> target[item] == cat }
                    feedback = context.getString(R.string.text_some_items_are_in_the_wrong_category) + " ($correctCount/" + question.items.size + ")"
                }
            }
            is VocabularyTestQuestion -> {
                val expected = normalizeText(question.correctAnswer)
                val given = normalizeText(currentState.selectedAnswer as? String)
                isCorrect = expected == given
                if (!isCorrect) feedback =
                    context.getString(R.string.text_the_correct_translation_is_2d, question.correctAnswer)
            }
        }

        if (isCorrect) {
            _exerciseSessionState.update { it?.copy(correctAnswers = it.correctAnswers + 1, answerResult = AnswerResult.CORRECT) }
        } else {
            _exerciseSessionState.update { it?.copy(wrongAnswers = it.wrongAnswers + 1, answerResult = AnswerResult.INCORRECT(feedback)) }
        }
    }

    fun nextQuestion() {
        val currentState = _exerciseSessionState.value ?: return
        val nextIndex = currentState.currentQuestionIndex + 1
        if (nextIndex >= currentState.questions.size) {
            _exerciseSessionState.update {
                it?.copy(
                    currentQuestionIndex = currentState.questions.size,
                    answerResult = AnswerResult.UNCHECKED
                )
            }
        } else {
            _exerciseSessionState.update {
                it?.copy(
                    currentQuestionIndex = nextIndex,
                    selectedAnswer = null,
                    answerResult = AnswerResult.UNCHECKED
                )
            }
        }
    }

    fun closeExercise() {
        _exerciseSessionState.value = null
    }

    fun deleteExercise(exerciseId: String) {
        viewModelScope.launch {
            exerciseRepository.deleteExercise(exerciseId)
        }
    }

    fun startYouTubeExercise(url: String, sourceLanguage: Language, targetLanguage: Language) {
        _youTubeSessionParams.value = YouTubeSessionParams(url = url, sourceLanguage = sourceLanguage.code, targetLanguage = targetLanguage.code)
        _youTubeExerciseState.value = YouTubeExerciseState.Idle
    }

    fun fetchSubtitlesForVideoId(videoId: String) {
        // Guard against re-fetching if already in progress or already successfully loaded.
        if (videoId == currentlyFetchingVideoId) {
            Log.i("ExerciseViewModel", "Already fetching subtitles for $videoId. Ignoring request.")
            return
        }
        val currentState = _youTubeExerciseState.value
        if (currentState is YouTubeExerciseState.Success && currentState.videoId == videoId) {
            Log.i("ExerciseViewModel", "Subtitles for $videoId are already loaded. Ignoring request.")
            return
        }

        val params = _youTubeSessionParams.value ?: run {
            Log.e("ExerciseViewModel", "No YouTube session params while trying to fetch subtitles for $videoId")
            _youTubeExerciseState.value = YouTubeExerciseState.Error("Session parameters not found. Please start the exercise again.")
            return
        }
        val langCode = params.sourceLanguage ?: "en" // Default to English

        Log.i("ExerciseViewModel", "Fetching subtitles for videoId=$videoId (preferredLang=$langCode)")

        // Cancel any ongoing subtitle translation job when starting a new fetch
        currentSubtitleTranslationJob?.cancel()

        viewModelScope.launch {
            currentlyFetchingVideoId = videoId
            _youTubeExerciseState.value = YouTubeExerciseState.Loading
            Log.d("ExerciseViewModel", "State set to Loading for videoId=$videoId")
            try {
                val title = youTubeApiService.getVideoTitle(videoId)
                Log.i("ExerciseViewModel", "Fetched video title: '${title.take(100)}'")

                var available: List<String> = emptyList()
                var attempts = 0
                // Retry fetching transcripts as the page might still be loading JS.
                while (attempts < 3 && available.isEmpty()) {
                    if (attempts > 0) {
                        Log.i("ExerciseViewModel", "Transcript list was empty. Retrying after delay... (Attempt ${attempts + 1}/3)")
                        delay(2500)
                    }
                    try {
                        available = youTubeExerciseService.listTranscripts(videoId)
                    } catch (e: Exception) {
                        Log.w("ExerciseViewModel", "listTranscripts failed on attempt $attempts", e)
                    }
                    attempts++
                }

                Log.i("ExerciseViewModel", "Available transcript languages for $videoId: $available")
                val chosenLang = when {
                    available.contains(langCode) -> langCode
                    available.contains("en") -> "en"
                    available.isNotEmpty() -> available.first()
                    else -> null
                }
                Log.d("ExerciseViewModel", "Chosen transcript language: $chosenLang (preferred was $langCode)")

                val finalLang = chosenLang ?: throw Exception("No transcripts available for this video.")
                val subtitles = youTubeExerciseService.getTranscript(videoId, finalLang)
                Log.i("ExerciseViewModel", "Fetched ${subtitles.size} subtitle lines for $videoId in lang=${finalLang}")

                _youTubeExerciseState.value = YouTubeExerciseState.Success(
                    videoId = videoId,
                    videoTitle = title,
                    subtitles = subtitles
                )
                Log.d("ExerciseViewModel", "YouTubeExerciseState set to Success for videoId=$videoId")

                // Kick off subtitle translations in background, prioritizing earliest lines first
                startTranslatingSubtitles(videoId)
            } catch (e: Exception) {
                Log.e("ExerciseViewModel", "Failed to fetch YouTube data for $videoId", e)
                _youTubeExerciseState.value = YouTubeExerciseState.Error(e.message ?: "An unknown error occurred.")
            } finally {
                // Clear the lock only if the fetch was for the current video.
                if(currentlyFetchingVideoId == videoId) {
                    currentlyFetchingVideoId = null
                }
                Log.d("ExerciseViewModel", "Fetch complete for videoId=$videoId; fetch lock released")
            }
        }
    }

    private fun startTranslatingSubtitles(videoId: String) {
        val params = _youTubeSessionParams.value
        val targetCode = params?.targetLanguage
        if (targetCode.isNullOrBlank()) {
            Log.w("ExerciseViewModel", "No target language set for YouTube exercise; skipping subtitle translation")
            return
        }

        currentSubtitleTranslationJob?.cancel()
        currentSubtitleTranslationJob = viewModelScope.launch {
            // Resolve target language by code
            val allLangs = try { languageRepository.loadMasterLanguages().first() } catch (_: Exception) { emptyList() }
            val targetLang = allLangs.firstOrNull { it.code.equals(targetCode, ignoreCase = true) }
            if (targetLang == null) {
                Log.w("ExerciseViewModel", "Could not resolve target Language for code=$targetCode; skipping subtitle translation")
                return@launch
            }

            val cache = mutableMapOf<String, String?>()

            fun updateLine(index: Int, translated: String) {
                val state = _youTubeExerciseState.value
                if (state is YouTubeExerciseState.Success && state.videoId == videoId) {
                    val updated = state.subtitles.toMutableList()
                    val old = updated.getOrNull(index)
                    if (old != null && old.translatedText != translated) {
                        updated[index] = old.copy(translatedText = translated)
                        _youTubeExerciseState.value = YouTubeExerciseState.Success(
                            videoId = state.videoId,
                            videoTitle = state.videoTitle,
                            subtitles = updated
                        )
                    }
                }
            }

            val initialState = _youTubeExerciseState.value as? YouTubeExerciseState.Success ?: return@launch
            val subtitles = initialState.subtitles
            // Ensure order by start time (should already be)
            val indices = subtitles.indices.sortedBy { subtitles[it].start }

            for (i in indices) {
                val current = (_youTubeExerciseState.value as? YouTubeExerciseState.Success) ?: break
                if (current.videoId != videoId) break // video changed

                val line = current.subtitles.getOrNull(i) ?: continue
                if (line.text.isBlank() || line.translatedText != null) continue

                val cached = cache[line.text]
                if (cached != null) {
                    updateLine(i, cached)
                    continue
                }

                val res = translationService.simpleTranslateTo(line.text, targetLang)
                if (res.isSuccess) {
                    val translated = res.getOrNull() ?: continue
                    cache[line.text] = translated
                    updateLine(i, translated)
                } else {
                    Log.w("ExerciseViewModel", "Subtitle translation failed at index=$i: ${res.exceptionOrNull()?.message}")
                }
                // Tiny pause to avoid hammering the server
                delay(50)
            }
            Log.i("ExerciseViewModel", "Subtitle translation finished for videoId=$videoId")
        }
    }

    fun clearYouTubeSubtitles() {
        Log.d("ExerciseViewModel", "Clearing YouTube subtitles and resetting state to Idle")
        currentlyFetchingVideoId = null
        currentSubtitleTranslationJob?.cancel()
        _youTubeExerciseState.value = YouTubeExerciseState.Idle
    }

    /**
     * Generates questions from YouTube video subtitles using AI and saves the exercise.
     * This should be called from ViewModel scope to avoid coroutine cancellation issues.
     */
    fun generateAndSaveYouTubeExercise(
        subtitles: List<SubtitleLine>,
        videoTitle: String,
        videoId: String,
        sourceLanguage: String?,
        targetLanguage: String?,
        onComplete: (Exercise) -> Unit,
        onError: (String) -> Unit
    ) {
        viewModelScope.launch {
            try {
                Log.i("ExerciseViewModel", "Generating AI questions for YouTube video: $videoTitle")

                val questions = exerciseService.generateQuestionsFromSubtitles(
                    subtitles = subtitles,
                    videoTitle = videoTitle,
                    sourceLanguage = sourceLanguage,
                    targetLanguage = targetLanguage
                ) { progress ->
                    Log.d("ExerciseViewModel", "Question generation progress: $progress")
                }

                if (questions.isEmpty()) {
                    onError("No questions could be generated from the video content")
                    return@launch
                }

                Log.i("ExerciseViewModel", "Successfully generated ${questions.size} questions from YouTube video")

                // Create the exercise with YouTube URL for future reference
                val youtubeUrl = _youTubeSessionParams.value?.url
                val exercise = Exercise(
                    id = "youtube_${videoId}_${System.currentTimeMillis()}",
                    title = "YouTube: $videoTitle",
                    questions = questions.map { it.id },
                    sourceLanguage = sourceLanguage,
                    targetLanguage = targetLanguage,
                    contextTitle = "YouTube Video: $videoTitle",
                    contextText = "Questions based on the YouTube video content.",
                    youtubeUrl = youtubeUrl
                )

                // Save the exercise and questions to repository
                exerciseRepository.saveNewExerciseWithQuestions(exercise, questions)

                Log.i("ExerciseViewModel", "Saved YouTube exercise '${exercise.title}' with ${questions.size} questions")

                onComplete(exercise)

            } catch (e: Exception) {
                Log.e("ExerciseViewModel", "Failed to generate and save YouTube exercise", e)
                onError(e.message ?: "Unknown error occurred while generating questions")
            }
        }
    }

    /**
     * Navigates back to YouTube video from an exercise session.
     * Restarts the YouTube exercise with the saved URL.
     */
    fun restartYouTubeExercise(youtubeUrl: String, sourceLanguage: String?, targetLanguage: String?) {
        // Update the YouTube session params to restart the exercise
        _youTubeSessionParams.value = YouTubeSessionParams(
            url = youtubeUrl,
            sourceLanguage = sourceLanguage,
            targetLanguage = targetLanguage
        )
        // Reset the exercise state to allow restarting
        _youTubeExerciseState.value = YouTubeExerciseState.Idle
    }
}
