@file:Suppress("HardCodedStringLiteral")

package eu.gaudian.translator.utils

import android.content.Context
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.VocabularyItem
import eu.gaudian.translator.model.communication.ApiManager
import eu.gaudian.translator.model.grammar.GrammaticalFeature
import eu.gaudian.translator.model.grammar.LanguageConfig
import eu.gaudian.translator.model.grammar.VocabularyFeatures
import eu.gaudian.translator.model.repository.LanguageConfigRepository
import eu.gaudian.translator.model.repository.SettingsRepository
import eu.gaudian.translator.utils.StringHelper.isSentenceStrict
import eu.gaudian.translator.viewmodel.MessageDisplayType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

class VocabularyService(context: Context) {

    private val apiRequestHandler = ApiRequestHandler(ApiManager(context), context)
    private val settingsRepository = SettingsRepository(context)
    private val languageConfigRepository = LanguageConfigRepository(context)
    private val jsonParser = Json { ignoreUnknownKeys = true; isLenient = true }

    suspend fun fetchZipfFrequency(word: String, languageCode: String): Result<Float> = withContext(Dispatchers.IO) {
        if (isSentenceStrict(word)) {
            return@withContext Result.failure(Exception("Cannot fetch frequency for a sentence"))
        }
        try {
            Log.d("VocabularyService", "Fetching zipf frequency for word '$word' in language '$languageCode'")

            val encodedWord = java.net.URLEncoder.encode(word, Charsets.UTF_8.name())
            val encodedLang = java.net.URLEncoder.encode(languageCode, Charsets.UTF_8.name())
            val urlString = "http://23.88.48.47:5100/zipf_frequency?word=$encodedWord&lang=$encodedLang"

            val url = java.net.URL(urlString)
            val connection = (url.openConnection() as java.net.HttpURLConnection).apply {
                requestMethod = "GET"
                connectTimeout = 8000
                readTimeout = 8000
            }

            try {
                val responseCode = connection.responseCode
                val responseMessage = connection.responseMessage
                val stream = if (responseCode in 200..299) connection.inputStream else connection.errorStream
                val body = stream?.bufferedReader()?.use { it.readText() } ?: ""

                if (responseCode in 200..299) {
                    val jsonObject = Json.parseToJsonElement(body).jsonObject
                    val zipfFrequency = jsonObject["zipf_frequency"]?.jsonPrimitive?.content?.toFloatOrNull()
                    if (zipfFrequency != null) {
                        Log.d("VocabularyService", "Successfully fetched zipf frequency: $zipfFrequency")
                        Result.success(zipfFrequency)
                    } else {
                        Log.w("VocabularyService", "Invalid zipf frequency response format: $body")
                        Result.failure(Exception("Invalid response format"))
                    }
                } else {
                    Log.w("VocabularyService", "Failed to fetch zipf frequency: $responseCode - $responseMessage | $body")
                    Result.failure(Exception("HTTP $responseCode: $responseMessage"))
                }
            } finally {
                connection.disconnect()
            }
        } catch (e: Exception) {
            Log.e("VocabularyService", "Error fetching zipf frequency: ${e.message}")
            Result.failure(e)
        }
    }

    suspend fun translateWordsBatch(
        words: List<String>,
        languageFirst: Language,
        languageSecond: Language
    ): Result<List<VocabularyItem>> {
        if (words.isEmpty()) return Result.success(emptyList())

        return try {
            Log.i("VocabularyService", "Translating batch of ${words.size} words from ${languageFirst.englishName} to ${languageSecond.englishName}")

            val template = VocabularyTranslationRequest(
                words = words.filter { it.isNotBlank() }.distinct(),
                languageFirst = languageFirst.englishName,
                languageSecond = languageSecond.englishName
            )

            val result = apiRequestHandler.executeRequest(template)

            result.map { apiResponse ->
                val mappedItems = apiResponse.flashcards.mapNotNull { flashcard ->
                    val frontIsLangFirst = flashcard.front.language.equals(languageFirst.englishName, true)
                    val backIsLangSecond = flashcard.back.language.equals(languageSecond.englishName, true)
                    val frontIsLangSecond = flashcard.front.language.equals(languageSecond.englishName, true)
                    val backIsLangFirst = flashcard.back.language.equals(languageFirst.englishName, true)
                    when {
                        frontIsLangFirst && backIsLangSecond -> VocabularyItem(
                            id = 0,
                            languageFirstId = languageFirst.nameResId,
                            languageSecondId = languageSecond.nameResId,
                            wordFirst = flashcard.front.word,
                            wordSecond = flashcard.back.word
                        )
                        frontIsLangSecond && backIsLangFirst -> VocabularyItem(
                            id = 0,
                            languageFirstId = languageFirst.nameResId,
                            languageSecondId = languageSecond.nameResId,
                            wordFirst = flashcard.back.word,
                            wordSecond = flashcard.front.word
                        )
                        else -> null
                    }
                }
                Log.i("VocabularyService", "Successfully translated ${mappedItems.size} out of ${words.size} words")
                mappedItems
            }.onFailure { exception ->
                Log.e("VocabularyService", "Failed to translate word batch", exception)
            }
        } catch (e: Exception) {
            Log.e("VocabularyService", "Unexpected error in translateWordsBatch", e)
            Result.failure(e)
        }
    }

    suspend fun generateVocabularyItems(
        category: String,
        languageFirst: Language,
        languageSecond: Language,
        amount: Int
    ): Result<List<VocabularyItem>> {
        return try {
            Log.i("VocabularyService", "Generating $amount vocabulary items for category '$category' (${languageFirst.englishName} -> ${languageSecond.englishName})")

            val settingsPrompt = settingsRepository.customPromptVocabulary.flow.first()

            val template = VocabularyGenerationRequest(
                category = category,
                languageFirst = languageFirst.englishName,
                languageSecond = languageSecond.englishName,
                amount = amount,
                customPrompt = settingsPrompt
            )

            val result = apiRequestHandler.executeRequest(template)

            result.map { apiResponse ->
                Log.d("VocabularyService", "API returned ${apiResponse.flashcards.size} flashcards. Now mapping to VocabularyItem model.")

                val mappedItems = apiResponse.flashcards.mapNotNull { flashcard ->
                    val frontIsLangFirst = flashcard.front.language.equals(languageFirst.englishName, ignoreCase = true)
                    val backIsLangSecond = flashcard.back.language.equals(languageSecond.englishName, ignoreCase = true)
                    val frontIsLangSecond = flashcard.front.language.equals(languageSecond.englishName, ignoreCase = true)
                    val backIsLangFirst = flashcard.back.language.equals(languageFirst.englishName, ignoreCase = true)

                    when {
                        frontIsLangFirst && backIsLangSecond -> VocabularyItem(
                            id = 0,
                            languageFirstId = languageFirst.nameResId,
                            languageSecondId = languageSecond.nameResId,
                            wordFirst = flashcard.front.word.trim(),
                            wordSecond = flashcard.back.word.trim()
                        )

                        frontIsLangSecond && backIsLangFirst -> VocabularyItem(
                            id = 0,
                            languageFirstId = languageFirst.nameResId,
                            languageSecondId = languageSecond.nameResId,
                            wordFirst = flashcard.back.word.trim(),
                            wordSecond = flashcard.front.word.trim()
                        )

                        else -> {
                            Log.w("VocabularyService", "SKIPPING flashcard. Mismatched languages. Expected '${languageFirst.englishName}/${languageSecond.englishName}', but API returned '${flashcard.front.language}/${flashcard.back.language}'.")
                            StatusMessageService.trigger(
                                StatusAction.ShowMessage(
                                    "SKIPPING flashcard. Mismatched languages. Expected '${languageFirst.englishName}/${languageSecond.englishName}', but API returned '${flashcard.front.language}/${flashcard.back.language}'.",
                                    MessageDisplayType.ERROR,
                                    5
                                )
                            )
                            null
                        }
                    }
                }

                Log.i("VocabularyService", "Successfully mapped ${mappedItems.size} out of ${apiResponse.flashcards.size} received flashcards.")
                mappedItems
            }.onFailure { exception ->
                Log.e("VocabularyService", "Vocabulary generation failed entirely.", exception)
                StatusMessageService.trigger(
                    StatusAction.ShowMessage(
                        "Vocabulary generation failed entirely: ${exception.message}", //TODO translate
                        MessageDisplayType.ERROR,
                        5
                    ))
            }
        } catch (e: Exception) {
            Log.e("VocabularyService", "Unexpected error in generateVocabularyItems", e)
            Result.failure(e)
        }
    }

    suspend fun generateSynonyms(
        term: String,
        translation: String,
        language: Language,
        translationLanguage: Language,
        amount: Int? = 5
    ): Result<List<SynonymObject>> = withContext(Dispatchers.IO) {
        try {
            Log.i("VocabularyService", "Generating $amount synonyms for term '$term' in ${language.englishName}")

            val template = SynonymGenerationRequest(
                amount = amount ?: 5,
                language = language.englishName,
                term = term,
                translation = translation,
                translationLanguage = translationLanguage.englishName,
                languageCode = language.code
            )

            val result = apiRequestHandler.executeRequest(template)

            result.map { response ->
                val synonyms = response.synonyms
                    .map { it.copy(word = it.word.trim().replace("\"", "")) }
                    .filter { it.word.isNotEmpty() }
                Log.i("VocabularyService", "Successfully generated ${synonyms.size} synonyms for term '$term'")
                synonyms
            }.onFailure { exception ->
                Log.e("VocabularyService", "Failed to generate synonyms for term '$term'", exception)
            }
        } catch (e: Exception) {
            Log.e("VocabularyService", "Unexpected error in generateSynonyms for term '$term'", e)
            Result.failure(e)
        }
    }

    suspend fun fetchAndApplyGrammaticalDetails(
        items: List<VocabularyItem>,
        languages: Map<Int, Language>
    ): Result<List<VocabularyItem>> = withContext(Dispatchers.IO) {
        if (items.isEmpty()) return@withContext Result.success(emptyList())

        val wordsToAnalyze = items.flatMap { item ->
            listOfNotNull(
                item.languageFirstId?.let { WordIdentifier(item.id, "first", item.wordFirst, it) },
                item.languageSecondId?.let { WordIdentifier(item.id, "second", item.wordSecond, it) }
            )
        }

        val classificationResult = classifyWords(wordsToAnalyze, languages)
        if (classificationResult.isFailure) {
            return@withContext Result.failure(classificationResult.exceptionOrNull()!!)
        }
        val classifiedWords = classificationResult.getOrNull() ?: return@withContext Result.success(items)

        val wordsWithFeaturesToFetch = mutableListOf<ClassifiedWord>()
        val wordsWithCategoryOnly = mutableListOf<ClassifiedWord>()

        classifiedWords.forEach { word ->
            val config = languageConfigRepository.getConfigForLanguage(word.languageCode)
            val categoryHasFields = config?.categories?.get(word.category)?.fields?.isNotEmpty() ?: false
            if (categoryHasFields) {
                wordsWithFeaturesToFetch.add(word)
            } else {
                wordsWithCategoryOnly.add(word)
            }
        }

        val groupedByLangAndCategory = wordsWithFeaturesToFetch.groupBy { Pair(it.languageCode, it.category) }

        val featureResults = groupedByLangAndCategory.map { (groupKey, words) ->
            async {
                val (langCode, category) = groupKey
                val languageConfig = languageConfigRepository.getConfigForLanguage(langCode)
                fetchFeaturesForGroup(words, languageConfig, category)
            }
        }.awaitAll().filterNotNull().flatten()

        val categoryOnlyResults = wordsWithCategoryOnly.map {
            WordWithFeatures(
                itemId = it.itemId,
                position = it.position,
                category = it.category,
                properties = emptyMap() // No properties to add
            )
        }

        val allResults = featureResults + categoryOnlyResults
        val finalItems = mergeFeaturesIntoItems(items, classifiedWords, allResults)

        Result.success(finalItems)
    }

    private suspend fun classifyWords(
        words: List<WordIdentifier>,
        languages: Map<Int, Language>
    ): Result<List<ClassifiedWord>> {
        // Short-circuit classification for sentences using StringHelper
        val preclassified = words.associateWith { word ->
            if (isSentenceStrict(word.word)) "sentence" else null
        }

        val wordsNeedingApi = words.filter { preclassified[it] == null }

        if (wordsNeedingApi.isEmpty()) {
            return Result.success(words.map {
                ClassifiedWord(
                    itemId = it.itemId,
                    position = it.position,
                    word = it.word,
                    languageCode = languages[it.languageId]?.code ?: "",
                    category = "sentence"
                )
            })
        }

        val wordsToClassify = wordsNeedingApi.map {
            WordToAnalyze(it.tempId, it.word, languages[it.languageId]?.englishName ?: "")
        }
        val languageConfigs = languageConfigRepository.configs.value
        val possibleCategories = languageConfigs.values.flatMap { it.categories.keys }.distinct()

        val wordsToClassifyJson = jsonParser.encodeToString(wordsToClassify)
        val template = GrammarClassificationRequest(wordsToClassifyJson, possibleCategories.joinToString(", "))

        return apiRequestHandler.executeRequest(template).map { response ->
            val apiClassified = response.results.mapNotNull { result ->
                val originalWord = wordsNeedingApi.find { it.tempId == result.id }
                originalWord?.let {
                    ClassifiedWord(
                        itemId = it.itemId,
                        position = it.position,
                        word = it.word,
                        languageCode = languages[it.languageId]?.code ?: "",
                        category = result.category
                    )
                }
            }
            // Add preclassified sentences
            val preclassifiedItems = words.filter { preclassified[it] != null }.map {
                ClassifiedWord(
                    itemId = it.itemId,
                    position = it.position,
                    word = it.word,
                    languageCode = languages[it.languageId]?.code ?: "",
                    category = preclassified[it] ?: ""
                )
            }
            apiClassified + preclassifiedItems
        }
    }

    private fun buildPromptForFeatures(
        words: List<ClassifiedWord>,
        languageConfig: LanguageConfig?,
        category: String
    ): String? {
        val categoryConfig = languageConfig?.categories?.get(category) ?: return null
        if (categoryConfig.fields.isEmpty()) return null

        val languageName = languageConfig.language_code
        val wordsJson = words.map { WordToAnalyzeFeatures(id = it.tempId, word = it.word) }

        val promptBuilder = PromptBuilder("Analyze the grammatical properties for each '$category' in the provided JSON array for the '$languageName' language.")

        categoryConfig.fields.forEach { field ->
            val propertyName = "'${field.key}'"
            if (field.type == "enum" && !field.options.isNullOrEmpty()) {
                val optionsString = field.options.joinToString(", ") { "'$it'" }
                promptBuilder.addDetail("For the $propertyName property, choose one of the following values: $optionsString.")
            } else {
                promptBuilder.addDetail("For the $propertyName property, provide the appropriate value. Do not return anything else. If the property is not applicable, return an empty response.")
            }
        }

        promptBuilder
            .withJsonResponse("a 'results' array, each with the original 'id' and a 'properties' object containing the found values.")
            .addDetail("Here is the JSON array: ${jsonParser.encodeToString(wordsJson)}")

        return promptBuilder.build()
    }

    private suspend fun fetchFeaturesForGroup(
        words: List<ClassifiedWord>,
        languageConfig: LanguageConfig?,
        category: String
    ): List<WordWithFeatures>? {
        val prompt = buildPromptForFeatures(words, languageConfig, category) ?: return null

        Log.d(prompt)

        val template = GrammarFeaturesRequest(prompt)

        return apiRequestHandler.executeRequest(template).map { response ->
            response.results.mapNotNull { result ->
                words.find { it.tempId == result.id }?.let { originalWord ->
                    @Suppress("UNCHECKED_CAST")
                    WordWithFeatures(
                        itemId = originalWord.itemId,
                        position = originalWord.position,
                        category = category,
                        properties = result.properties.filterValues { it != null } as Map<String, String>
                    )
                }
            }
        }.getOrNull()
    }

    private fun mergeFeaturesIntoItems(
        originalItems: List<VocabularyItem>,
        classifiedWords: List<ClassifiedWord>,
        featureResults: List<WordWithFeatures>
    ): List<VocabularyItem> {
        val featuresMap = featureResults.associateBy { Pair(it.itemId, it.position) }
        val classifiedMap = classifiedWords.associateBy { Pair(it.itemId, it.position) }
        val itemsMap = originalItems.associateBy { it.id }.toMutableMap()

        classifiedMap.forEach { (key, classifiedWord) ->
            val (itemId, position) = key
            val currentItem = itemsMap[itemId] ?: return@forEach

            val currentFeatures = currentItem.features?.let {
                try { jsonParser.decodeFromString<VocabularyFeatures>(it) } catch (_: Exception) { VocabularyFeatures() }
            } ?: VocabularyFeatures()

            val wordWithFeatures = featuresMap[key]
            val newGrammaticalFeature = GrammaticalFeature(
                category = wordWithFeatures?.category ?: classifiedWord.category,
                properties = wordWithFeatures?.properties ?: emptyMap()
            )

            var finalWord = classifiedWord.word
            val languageConfig = languageConfigRepository.getConfigForLanguage(classifiedWord.languageCode)
            val categoryConfig = languageConfig?.categories?.get(newGrammaticalFeature.category)

            if (!isSentenceStrict(finalWord)) {
                if (newGrammaticalFeature.category == "noun" && categoryConfig != null) {
                    // Prefer explicit mapping based on detected gender; otherwise fall back to removing any configured leading articles
                    val gender = newGrammaticalFeature.properties["gender"]
                    val mappedArticle = categoryConfig.mappings?.get("gender")?.get(gender)
                    finalWord = when {
                        mappedArticle != null -> StringHelper.removeLeadingArticlesOneOf(finalWord, listOf(mappedArticle))
                        !languageConfig.articles.isNullOrEmpty() -> StringHelper.removeLeadingArticlesOneOf(
                            finalWord,
                            languageConfig.articles
                        )
                        else -> finalWord
                    }
                }
            }

            val updatedFeatures = if (position == "first") {
                currentFeatures.copy(first = newGrammaticalFeature)
            } else {
                currentFeatures.copy(second = newGrammaticalFeature)
            }

            val updatedItem = if (position == "first") {
                currentItem.copy(
                    wordFirst = finalWord,
                    features = jsonParser.encodeToString(updatedFeatures)
                )
            } else {
                currentItem.copy(
                    wordSecond = finalWord,
                    features = jsonParser.encodeToString(updatedFeatures)
                )
            }
            itemsMap[itemId] = updatedItem
        }

        return originalItems.map { itemsMap[it.id] ?: it }
    }

    // Data classes for API responses
    @Serializable
    data class SynonymApiResponse(
        val synonyms: List<SynonymObject>
    )

    @Serializable
    data class SynonymObject(
        val word: String,
        val proximity: Int
    )

    @Serializable
    data class BatchClassificationResponse(
        val results: List<ClassificationResult>
    )

    @Serializable
    data class ClassificationResult(
        val id: Int,
        val category: String
    )

    @Serializable
    data class WordToAnalyze(
        val id: Int,
        val word: String,
        val language: String
    )

    @Serializable
    data class WordToAnalyzeFeatures(
        val id: Int,
        val word: String
    )

    @Serializable
    data class BatchGrammarResponse(
        val results: List<AnalyzedWordResult>
    )

    @Serializable
    data class AnalyzedWordResult(
        val id: Int,
        val properties: Map<String, String?>
    )

    // Internal data classes
    private data class WordIdentifier(
        val itemId: Int,
        val position: String, // "first" or "second"
        val word: String,
        val languageId: Int
    ) {
        val tempId = "item${itemId}_$position".hashCode()
    }

    private data class ClassifiedWord(
        val itemId: Int,
        val position: String,
        val word: String,
        val languageCode: String,
        val category: String
    ) {
        val tempId = "item${itemId}_$position".hashCode()
    }

    private data class WordWithFeatures(
        val itemId: Int,
        val position: String,
        val category: String,
        val properties: Map<String, String>
    )
}