package com.tomhempel.rbarometer.ui

import android.content.Intent
import android.widget.Button
import android.widget.Toast
import com.tomhempel.rbarometer.LanguageManager
import com.tomhempel.rbarometer.MainActivity
import com.tomhempel.rbarometer.data.ClientAnswersCache
import com.tomhempel.rbarometer.data.ClientRepository
import com.tomhempel.rbarometer.data.CompletedQuestionnaireRepository
import kotlinx.coroutines.*
import com.tomhempel.rbarometer.data.CompletedQuestionnaire
import com.tomhempel.rbarometer.network.NetworkUtils
import com.tomhempel.rbarometer.network.QuestionnaireAnswerSync
import com.tomhempel.rbarometer.network.QuestionnaireCache
import com.tomhempel.rbarometer.network.TokenStore
import com.tomhempel.rbarometer.questionnaire.GlobalValues
import com.tomhempel.rbarometer.questionnaire.QuestionItem

class LoadButtonHandler(
    private val activity: MainActivity,
    private val clientCodeProvider: () -> String,
    private val languageIDProvider: () -> String,
    private val questionnaireEntriesProvider: () -> List<QuestionItem.QuestionnaireEntry>,
    private val dynamicButtonsProvider: () -> List<Button>,
    private val onCompletedQuestionnaireIds: (Set<String>) -> Unit,
    private val updateButtonTexts: () -> Unit,
    private val setButtonsEnabled: (List<Button>) -> Unit,
    private val updateMainButtonsState: (Boolean) -> Unit,
    private val onPendingUploadIds: (Set<String>) -> Unit = {},
    private val onLoadStarted: () -> Unit = {},
    private val onLoadFinished: () -> Unit = {},
) {

    companion object {
        private const val DEV_SETTINGS_SECRET = DevSettingsAccess.CLIENT_CODE
    }

    private val loadScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
    private var loadJob: Job? = null
    private var loadGeneration = 0

    fun triggerLoad() {
        val inputText = clientCodeProvider().trim()
        if (inputText.isBlank()) {
            val message = LanguageManager.getText(languageIDProvider(), "please_client_code")
            Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
            return
        }

        if (inputText.equals(DEV_SETTINGS_SECRET, ignoreCase = true)) {
            if (!DevSettingsAccess.isAssigned(activity)) {
                val message = LanguageManager.getText(languageIDProvider(), "no_profile")
                Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
                return
            }
            GlobalValues.LAST_CLIENT_CODE = null
            GlobalValues.LOADED_CLIENT_CODE = null
            activity.markOpeningScreenReinit()
            activity.startActivity(Intent(activity, DevSettingsActivity::class.java))
            return
        }

        val sameClientAlreadyLoaded = inputText.equals(GlobalValues.LOADED_CLIENT_CODE, ignoreCase = true)
        if (!sameClientAlreadyLoaded) {
            onCompletedQuestionnaireIds(emptySet())
            setButtonsEnabled(emptyList())
        }

        val clientCode = inputText
        GlobalValues.LAST_CLIENT_CODE = clientCode

        val assignedClients = QuestionnaireCache.getAssignedClients(activity)
        if (assignedClients.isEmpty() || assignedClients.none { it.equals(clientCode, ignoreCase = true) }) {
            GlobalValues.LOADED_CLIENT_CODE = null
            val message = LanguageManager.getText(languageIDProvider(), "no_profile")
            Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
            setButtonsEnabled(emptyList())
            updateMainButtonsState(false)
            updateButtonTexts()
            return
        }

        val generation = ++loadGeneration
        loadJob?.cancel()
        loadJob = loadScope.launch {
            try {
                runLoad(clientCode, sameClientAlreadyLoaded)
            } finally {
                if (generation == loadGeneration && isActive) {
                    withContext(Dispatchers.Main) { onLoadFinished() }
                }
            }
        }
    }

    private suspend fun runLoad(clientCode: String, sameClientAlreadyLoaded: Boolean) {
        val token = TokenStore.getToken(activity)
        val needsNetworkPull = !token.isNullOrBlank() && NetworkUtils.isOnline(activity) &&
            QuestionnaireAnswerSync.needsPull(activity, clientCode)

        if (needsNetworkPull) {
            withContext(Dispatchers.Main) { onLoadStarted() }
        }

        if (!sameClientAlreadyLoaded) {
            ClientAnswersCache.invalidate(clientCode)
        }
        ClientRepository.ensureClient(clientCode)
        ClientAnswersCache.allForClient(clientCode)

        GlobalValues.LOADED_CLIENT_CODE = clientCode
        withContext(Dispatchers.Main) { updateMainButtonsState(true) }

        if (needsNetworkPull) {
            QuestionnaireAnswerSync.pullForClient(activity, token!!, clientCode, force = true)
            ClientAnswersCache.invalidate(clientCode)
            ClientAnswersCache.allForClient(clientCode)
        }

        handleNormalLoad(clientCode)
    }


    private suspend fun evaluateCondition(
        condition: QuestionItem.Condition?,
        clientCode: String,
        completedEntries: List<CompletedQuestionnaire>
    ): Boolean {
        if (condition == null) return false
        return when (condition) {
            is QuestionItem.Condition.AlwaysAvailable -> true
            is QuestionItem.Condition.RequiresCompleted -> {
                val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) }
                condition.required.all { req ->
                    val nReq = normalizeQuestionnaireId(req)
                    normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) }
                }
            }
            is QuestionItem.Condition.QuestionCondition -> {
                val answers = ClientAnswersCache.answersForQuestionnaire(clientCode, condition.questionnaire)
                val relevant = answers.find { it.questionId.endsWith(condition.questionId, ignoreCase = true) }
                val answerValue = relevant?.answerValue ?: ""
                when (condition.operator) {
                    "==" -> answerValue == condition.value
                    "!=" -> answerValue != condition.value
                    else -> false
                }
            }
            is QuestionItem.Condition.Combined -> {
                val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) }
                val reqOk = condition.requiresCompleted.isNullOrEmpty() || condition.requiresCompleted.all { req ->
                    val nReq = normalizeQuestionnaireId(req)
                    normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) }
                }
                if (!reqOk) return false
                val q = condition.questionCheck ?: return true
                val answers = ClientAnswersCache.answersForQuestionnaire(clientCode, q.questionnaire)
                val relevant = answers.find { it.questionId.endsWith(q.questionId, ignoreCase = true) }
                val answerValue = relevant?.answerValue ?: ""
                when (q.operator) {
                    "==" -> answerValue == q.value
                    "!=" -> answerValue != q.value
                    else -> false
                }
            }
            is QuestionItem.Condition.AnyOf -> {
                condition.conditions.any { evaluateCondition(it, clientCode, completedEntries) }
            }
        }
    }

    private fun normalizeQuestionnaireId(name: String): String =
        name.lowercase().removeSuffix(".json")

    private suspend fun handleNormalLoad(clientCode: String) {
        val completedEntries = withContext(Dispatchers.IO) {
            CompletedQuestionnaireRepository.getAllForClient(clientCode)
        }

        val pendingIds = completedEntries
            .filter { it.isDone && (it.uploadedAt == null || (it.uploadedAt ?: 0L) < it.timestamp) }
            .map { it.questionnaireId }
            .toSet()
        withContext(Dispatchers.Main) { onPendingUploadIds(pendingIds) }

        val completedIds = completedEntries
            .filter { it.isDone }
            .map { it.questionnaireId }
            .toSet()
        withContext(Dispatchers.Main) { onCompletedQuestionnaireIds(completedIds) }

        val enabledButtons = mutableListOf<Button>()
        val questionnaireEntries = questionnaireEntriesProvider()
        val dynamicButtons = dynamicButtonsProvider()

        for ((idx, entry) in questionnaireEntries.withIndex()) {
            val button = dynamicButtons.getOrNull(idx) ?: continue

            val isCompleted = completedEntries.any { completed ->
                normalizeQuestionnaireId(completed.questionnaireId).let { completedNorm ->
                    val targetNorm = normalizeQuestionnaireId(entry.file)
                    (completedNorm.contains(targetNorm) || targetNorm.contains(completedNorm)) && completed.isDone
                }
            }

            val condMet = evaluateCondition(entry.condition, clientCode, completedEntries)
            if (condMet || isCompleted) enabledButtons.add(button)
        }

        withContext(Dispatchers.Main) {
            setButtonsEnabled(enabledButtons)
            updateButtonTexts()
        }
    }

}
