@file:Suppress("HardCodedStringLiteral", "RegExpRedundantEscape")

package eu.gaudian.translator.utils

import eu.gaudian.translator.model.CategorizationQuestion
import eu.gaudian.translator.model.FillInTheBlankQuestion
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.TrueFalseQuestion
import eu.gaudian.translator.model.VocabularyTestQuestion
import eu.gaudian.translator.model.WordOrderQuestion
import org.json.JSONObject
import java.util.UUID
import kotlin.reflect.KClass

object ExercisePromptGenerator {

    fun generateVocabularyPrompt(exerciseTitle: String, languageFirst: String, languageSecond: String): String {
        return """
            Generate exactly 10 unique and relevant vocabulary flashcards related to the exercise topic: "$exerciseTitle".
            The languages are $languageFirst and $languageSecond.
            Return a single JSON object where keys are the words in "$languageFirst" and values are their translations in "$languageSecond".
        """.trimIndent()
    }

    fun generateExerciseShellPrompt(category: String): String {
        return """
            Generate a concise title for an language exercise about "$category".
            Return a single JSON object with the key "exerciseTitle".
        """.trimIndent()
    }

    fun generateContextPrompt(
        category: String,
        difficulty: String,
        languageFirst: String?,
        languageSecond: String?
    ): String {
        val langInfo = if (languageFirst != null && languageSecond != null) {
            "The text must be in '$languageSecond' (target language). If a dialogue is used, speakers can be generic (A/B). Optionally provide inline glosses or simple vocabulary in $languageFirst if needed in the context title only."
        } else ""
        val style = if (difficulty.equals("easy", true)) {
            "Write a very short, rudimentary 2-4 line dialogue suitable for beginners."
        } else if (difficulty.equals("hard", true)) {
            "Write a more complex paragraph (5-8 sentences) on the topic with natural language and a few advanced structures."
        } else {
            "Write a short paragraph (3-5 sentences) or a simple dialogue."
        }
        return """
            Create a self-contained context text for an exercise about "$category".
            $style
            $langInfo
            Return a JSON object with keys:
            - "contextTitle": a short title for the text (string),
            - "contextText": the full text (string).
        """.trimIndent()
    }

    fun generateQuestionsPrompt(
        exerciseTitle: String,
        optionalVocabulary: List<String>,
        questionTypes: List<KClass<out Question>>,
        difficulty: String,
        amount: Int,
        languageFirst: String,
        languageSecond: String,
        contextText: String? = null
    ): String {
        Log.d("ExercisePromptGenerator", "Generating questions for exercise: $exerciseTitle, difficulty: $difficulty, amount: $amount, types: ${questionTypes.joinToString(", ")}, context: $contextText, vocab: $optionalVocabulary, languageFirst: $languageFirst, languageSecond: $languageSecond")
        val typeNames = questionTypes.joinToString(", ") { it.simpleName ?: "question" }
        return """
            Generate a related set of $amount of Duolingo-like questions for an exercise titled "$exerciseTitle".
            The difficulty should be "$difficulty".
            The question types should be: $typeNames.
            include the following vocabulary words: ${optionalVocabulary.joinToString(", ")}.${if (optionalVocabulary.isNotEmpty()) " (Use them where natural.)" else ""}
            ${if (contextText != null) "All questions MUST be answerable solely based on the following passage. Use it as the context and do NOT require external knowledge.\nPASSAGE:\n\"\"\"$contextText\"\"\"" else "Generate questions that are coherent as a set."}
            "The exercise should use the languages '$languageFirst' (from) and '$languageSecond' (to). Use these languages for all question content and translations. For VocabularyTestQuestion, set languageDirection to \"$languageFirst -> $languageSecond\"."

            Return a single JSON object containing one key, "questions", which holds a JSON array of the question objects.
            Each object in the array must have a "type" field and a "name" field (the question prompt).
            Based on the "type", include these additional fields:
            - For "TrueFalseQuestion": "correctAnswer" (boolean), "explanation" (string explaining why the statement is true or false, aimed at a learner; keep it concise).
            - For "MultipleChoiceQuestion": "options" (array of strings), "correctAnswerIndex" (integer).
            - For "FillInTheBlankQuestion": "name" should contain "___" for the blank, and add a "correctAnswer" (string) field. Also include optional "hintBaseForm" (e.g., base verb like "to be") and "hintOptions" (array of 2-4 strings including the correct word and plausible distractors).
            - For "WordOrderQuestion": "words" (array of scrambled strings), "correctOrder" (array of strings in the correct order to form the sequence).
            - For "MatchingPairsQuestion": "pairs" (a JSON object of key-value strings to match) 5 pairs max.
            - For "ListeningComprehensionQuestion": "name" (the sentence to speak), "languageCode" (language code the sentence is in e.g., "en-US").
            - For "CategorizationQuestion": "items" (array of at least 6 strings to categorize), "categories" (array of at least 2 category names, fitting to the topic of the exercise), "correctMapping" (a JSON object mapping each item to its category).
            - For "VocabularyTestQuestion": "name" (the word to translate), "correctAnswer" (the translation), "languageDirection" (string like "$languageFirst -> $languageSecond").
        """.trimIndent()
    }
}

object ExerciseParser {

    fun parseExerciseContext(response: String): Pair<String, String>? {
        // Be robust to minor JSON issues from LLMs (smart quotes, trailing commas, code fences)
        fun sanitize(input: String): String {
            var s = input
            // Strip Markdown code fences if present
            s = s.replace("^```[a-zA-Z0-9_\\-]*\\s*".toRegex(), "")
                .replace("\\s*```$".toRegex(), "")
            // Normalize smart quotes to ASCII
            s = s.replace("[“”]".toRegex(), "\"")
                .replace("[‘’]".toRegex(), "'")
            // Remove trailing commas before } or ]
            s = s.replace(",\\s*(?=[}\\]])".toRegex(), "")
            return s.trim()
        }
        return try {
            val cleaned = cleanJsonString(response)
            val sanitized = sanitize(cleaned)
            val jsonObject = JSONObject(sanitized)
            val title = if (jsonObject.has("contextTitle")) jsonObject.optString("contextTitle", "") else ""
            val text = jsonObject.optString("contextText", "")
            if (text.isNotBlank()) Pair(title, text) else throw Exception("contextText missing")
        } catch (e: Exception) {
            // Fallback: try relaxed regex extraction
            return try {
                val sanitized = sanitize(cleanJsonString(response))
                val titleRegex = "\\\"contextTitle\\\"\\s*:\\s*\\\"([\\s\\S]*?)\\\"".toRegex()
                val textRegex = "\\\"contextText\\\"\\s*:\\s*\\\"([\\s\\S]*?)\\\"".toRegex()
                val titleMatch = titleRegex.find(sanitized)
                val textMatch = textRegex.find(sanitized)
                val title = titleMatch?.groups?.get(1)?.value ?: ""
                val text = textMatch?.groups?.get(1)?.value ?: ""
                if (text.isNotBlank()) Pair(title, text) else throw Exception("Regex fallback failed")
            } catch (_: Exception) {
                Log.e("ExerciseParser", "Failed to parse exercise context: $response", e)
                null
            }
        }
    }

    private fun cleanJsonString(response: String): String {
        val firstBrace = response.indexOf('{')
        val firstBracket = response.indexOf('[')

        val startIndex = when {
            firstBrace == -1 -> firstBracket
            firstBracket == -1 -> firstBrace
            else -> minOf(firstBrace, firstBracket)
        }

        if (startIndex == -1) return response

        val lastBrace = response.lastIndexOf('}')
        val lastBracket = response.lastIndexOf(']')

        val endIndex = maxOf(lastBrace, lastBracket)

        if (endIndex == -1) return response

        return response.substring(startIndex, endIndex + 1)
    }

    fun parseExerciseShell(response: String): Pair<String, String>? {
        return try {
            val cleanedJson = cleanJsonString(response)
            val jsonObject = JSONObject(cleanedJson)
            val id = UUID.randomUUID().toString()
            val title = jsonObject.getString("exerciseTitle")
            Pair(id, title)
        } catch (e: Exception) {
            Log.e("ExerciseParser", "Failed to parse exercise shell: $response", e)
            null
        }
    }

    fun parseQuestions(response: String): List<Question> {
        return try {
            val cleanedJson = cleanJsonString(response)
            val rootObject = JSONObject(cleanedJson)
            val jsonArray = rootObject.getJSONArray("questions")

            val questions = mutableListOf<Question>()
            var nextId = (1..10000).random()

            for (i in 0 until jsonArray.length()) {
                val qObject = jsonArray.getJSONObject(i)
                val type = qObject.getString("type")

                // Provide sensible defaults for missing names to prevent parse failures
                val defaultName = when (type) {
                    "WordOrderQuestion" -> "Tap the words below to form the sentence"
                    "MatchingPairsQuestion" -> "Match the pairs"
                    "CategorizationQuestion" -> "Categorize the items"
                    else -> ""
                }
                val name = qObject.optString("name", defaultName)

                val question: Question? = when (type) {
                    "TrueFalseQuestion" -> TrueFalseQuestion(
                        id = nextId++,
                        name = name,
                        correctAnswer = qObject.getBoolean("correctAnswer"),
                        explanation = if (qObject.has("explanation")) qObject.getString("explanation") else ""
                    )
                    "MultipleChoiceQuestion" -> {
                        val options = qObject.getJSONArray("options").let { arr -> (0 until arr.length()).map { arr.getString(it) } }
                        MultipleChoiceQuestion(
                            id = nextId++, name = name, options = options,
                            correctAnswerIndex = qObject.getInt("correctAnswerIndex")
                        )
                    }
                    "FillInTheBlankQuestion" -> FillInTheBlankQuestion(
                        id = nextId++, name = name,
                        correctAnswer = qObject.getString("correctAnswer"),
                        hintBaseForm = if (qObject.has("hintBaseForm")) qObject.getString("hintBaseForm") else "",
                        hintOptions = if (qObject.has("hintOptions")) qObject.getJSONArray("hintOptions").let { arr -> (0 until arr.length()).map { arr.getString(it) } } else emptyList()
                    )
                    "WordOrderQuestion" -> {
                        val words = qObject.getJSONArray("words").let { arr -> (0 until arr.length()).map { arr.getString(it) } }
                        val correctOrder = qObject.getJSONArray("correctOrder").let { arr -> (0 until arr.length()).map { arr.getString(it) } }
                        WordOrderQuestion(id = nextId++, name = name, words = words, correctOrder = correctOrder)
                    }
                    "MatchingPairsQuestion" -> {
                        val pairs = qObject.getJSONObject("pairs").let { obj -> obj.keys().asSequence().associateWith { obj.getString(it) } }
                        MatchingPairsQuestion(id = nextId++, name = name, pairs = pairs)
                    }
                    "ListeningComprehensionQuestion" -> ListeningComprehensionQuestion(
                        id = nextId++, name = name,
                        languageCode = qObject.getString("languageCode")
                    )
                    "CategorizationQuestion" -> {
                        val items = qObject.getJSONArray("items").let { arr -> (0 until arr.length()).map { arr.getString(it) } }
                        val categories = qObject.getJSONArray("categories").let { arr -> (0 until arr.length()).map { arr.getString(it) } }
                        val mapping = qObject.getJSONObject("correctMapping").let { obj -> obj.keys().asSequence().associateWith { obj.getString(it) } }
                        CategorizationQuestion(id = nextId++, name = name, items = items, categories = categories, correctMapping = mapping)
                    }
                    "VocabularyTestQuestion" -> VocabularyTestQuestion(
                        id = nextId++,
                        name = name,
                        correctAnswer = qObject.getString("correctAnswer"),
                        languageDirection = qObject.getString("languageDirection")
                    )
                    else -> null
                }
                question?.let { questions.add(it) }
            }
            questions
        } catch (e: Exception) {
            Log.e("ExerciseParser", "Failed to parse questions: $response", e)
            emptyList()
        }
    }
}