package com.tomhempel.rbarometer.network

import android.content.Context
import com.tomhempel.rbarometer.data.AnswerRepository
import com.tomhempel.rbarometer.data.CompletedQuestionnaireRepository
import com.tomhempel.rbarometer.questionnaire.AnswerKeyUtils
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser

object QuestionnaireSubmitMapper {
    private const val TAG = "SubmitMapper"

    suspend fun buildRequest(
        context: Context,
        clientCode: String,
        questionnaireId: String
    ): SubmitQuestionnaireRequest? {
        val detailJson = QuestionnaireCache.loadQuestionnaireJson(context, questionnaireId)
        if (detailJson == null) {
            SyncLog.w(TAG, "No cached questionnaire JSON for $questionnaireId")
            return null
        }
        val detail = JsonParser.parseString(detailJson).asJsonObject
        val questions = detail.getAsJsonArray("questions") ?: return null
        val answers = AnswerRepository.getAnswersForClientAndQuestionnaire(clientCode, questionnaireId)
            .associateBy(
                { AnswerKeyUtils.lookupKey(questionnaireId, it.questionId) },
                { it.answerValue },
            )
        val completed = CompletedQuestionnaireRepository.getStatus(clientCode, questionnaireId)

        val submitAnswers = mutableListOf<SubmitAnswer>()
        questions.forEach { questionElement ->
            val question = questionElement.asJsonObject
            val questionId = question.get("id")?.asString ?: return@forEach
            val layout = question.get("layout")?.asString.orEmpty()

            resolveStoredAnswer(answers, question)?.let { value ->
                submitAnswers += mapAnswer(questionId, layout, value, optionKeys(question))
            }

            val symptoms = question.getAsJsonArray("symptoms")
            symptoms?.forEach { symptom ->
                val symptomKey = symptom.asString
                answers[symptomKey]?.let { value ->
                    submitAnswers += SubmitAnswer(
                        questionID = symptomKey,
                        answerOptionKey = value.takeIf { it.isNotBlank() }
                    )
                }
            }
        }

        if (submitAnswers.isEmpty()) {
            SyncLog.w(
                TAG,
                "No mappable answers for ${SyncLog.clientRef(clientCode)} / $questionnaireId " +
                    "(stored keys count=${answers.size}, questions: ${questions.size()})"
            )
            return null
        }

        val structureRevision = detail.get("structureRevision")?.asInt?.coerceAtLeast(1)

        return SubmitQuestionnaireRequest(
            questionnaireID = questionnaireId,
            clientCode = clientCode,
            completedAt = completed?.timestamp?.let { it / 1000 },
            structureRevision = structureRevision,
            answers = submitAnswers
        )
    }

    /**
     * Resolve a stored answer for an API question object.
     * Saving uses [question] (stable key) in most handlers; some use [id] (short local id).
     */
    private fun resolveStoredAnswer(answers: Map<String, String>, question: JsonObject): String? {
        val questionKey = question.get("question")?.asString?.takeIf { it.isNotBlank() }
        val shortId = question.get("id")?.asString?.takeIf { it.isNotBlank() }
        if (questionKey != null && questionKey != shortId) {
            return answers[questionKey]
        }
        return shortId?.let { answers[it] }
    }

    private fun mapAnswer(
        questionId: String,
        layout: String,
        rawValue: String,
        optionKeys: Set<String>
    ): List<SubmitAnswer> {
        val value = rawValue.trim()
        if (value.isBlank()) return emptyList()

        return when (layout) {
            "radio_question" -> listOf(
                if (optionKeys.isEmpty() || optionKeys.contains(value)) {
                    SubmitAnswer(questionID = questionId, answerOptionKey = value)
                } else {
                    SubmitAnswer(questionID = questionId, freeTextValue = value)
                }
            )
            // Country lists and similar spinners store choices in question config.options,
            // not in answer_option rows — server accepts freeTextValue (see app_submit_validate).
            "string_spinner" -> listOf(
                SubmitAnswer(questionID = questionId, freeTextValue = value)
            )
            "multi_check_box_question" -> parseMultiValue(value).map {
                SubmitAnswer(questionID = questionId, answerOptionKey = it)
            }
            "value_spinner", "slider_question" -> listOf(
                SubmitAnswer(questionID = questionId, numericValue = value.toDoubleOrNull())
            )
            "date_spinner", "free_text" -> listOf(
                SubmitAnswer(questionID = questionId, freeTextValue = value)
            )
            else -> listOf(
                if (optionKeys.contains(value)) {
                    SubmitAnswer(questionID = questionId, answerOptionKey = value)
                } else {
                    SubmitAnswer(
                        questionID = questionId,
                        numericValue = value.toDoubleOrNull(),
                        freeTextValue = value.takeUnless { value.toDoubleOrNull() != null }
                    )
                }
            )
        }
    }

    private fun optionKeys(question: JsonObject): Set<String> {
        val options = question.getAsJsonArray("options") ?: return emptySet()
        return options.mapNotNull { option ->
            when {
                option.isJsonObject -> option.asJsonObject.get("key")?.asString
                option.isJsonPrimitive -> option.asString
                else -> null
            }
        }.toSet()
    }

    private fun parseMultiValue(value: String): List<String> {
        val trimmed = value.trim().removePrefix("[").removeSuffix("]")
        return trimmed.split(",", ";")
            .map { it.trim().trim('"', '\'') }
            .filter { it.isNotBlank() }
    }
}
