package com.tomhempel.rbarometer.session

import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import com.google.android.material.button.MaterialButton
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.tomhempel.rbarometer.MainActivity
import com.tomhempel.rbarometer.R
import com.tomhempel.rbarometer.network.AssignedClientInfo
import com.tomhempel.rbarometer.network.ProgramProgress
import com.tomhempel.rbarometer.network.ProgramSessionDefinition
import com.tomhempel.rbarometer.network.QuestionnaireCache
import com.tomhempel.rbarometer.questionnaire.GlobalValues
import com.tomhempel.rbarometer.questionnaire.QuestionnaireGeneric
import com.tomhempel.rbarometer.questionnaire.QuestionnaireLanguagePicker
import com.tomhempel.rbarometer.questionnaire.QuestionnairePersistence
import com.tomhempel.rbarometer.ui.AppLanguageStore
import com.tomhempel.rbarometer.ui.SessionMeasureUi
import com.tomhempel.rbarometer.utils.ViewUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class SessionFlowController(
    private val activity: MainActivity,
    private var languageID: String,
    private val clientCode: String,
    private val onExit: (SessionCompletionResult?) -> Unit,
) {
    private var session: ProgramSessionDefinition? = null
    private var progress: ProgramProgress? = null
    private var clientInfo: AssignedClientInfo? = null
    private var measures: List<SessionProgramHelper.SessionMeasure> = emptyList()
    private var measureIndex = 0
    private var pendingUpload = false

    fun start() {
        CoroutineScope(Dispatchers.Main).launch {
            if (!withContext(Dispatchers.IO) { loadSessionState() }) {
                showErrorAndExit(hubFallback("Keine Sitzung konfiguriert", "No session configured"))
                return@launch
            }
            measureIndex = resolveStartingMeasureIndex()
            pendingUpload = withContext(Dispatchers.IO) {
                SessionProgramHelper.clientHasPendingUpload(activity, clientCode)
            }
            when {
                measures.all { it.completedInCycle } && pendingUpload -> {
                    measureIndex = measureIndex.coerceIn(0, (measures.size - 1).coerceAtLeast(0))
                    showMeasureInterstitial(editBeforeUpload = true)
                }
                measures.all { it.completedInCycle } -> returnToMainWithCompletionDialog()
                else -> showMeasureInterstitial()
            }
        }
    }

    fun handleQuestionnaireFinished(
        wasCompleted: Boolean,
        draftSnapshot: QuestionnaireDraftSnapshot? = null,
    ): Boolean {
        CoroutineScope(Dispatchers.Main).launch {
            withContext(Dispatchers.IO) {
                if (!wasCompleted) {
                    saveDraftSnapshot(draftSnapshot)
                }
                reloadMeasures()
                refreshPendingUploadFlag()
            }
            if (wasCompleted) {
                if (measures.all { it.completedInCycle }) {
                    returnToMainWithCompletionDialog()
                } else {
                    measureIndex++
                    advancePastCompletedMeasures()
                }
            } else {
                persistPauseState()
                val resumeEdit = pendingUpload && measures.all { it.completedInCycle }
                showMeasureInterstitial(editBeforeUpload = resumeEdit)
            }
        }
        return true
    }

    private fun resolveStartingMeasureIndex(): Int {
        val prog = progress ?: return 0
        val sess = session ?: return 0
        val paused = SessionPauseStore.load(activity, clientCode)
        if (
            paused != null &&
            paused.sessionId == sess.sessionID &&
            paused.cycleNumber == prog.cycleNumber &&
            paused.cycleStartedAt == prog.cycleStartedAt
        ) {
            return paused.measureIndex.coerceIn(0, (measures.size - 1).coerceAtLeast(0))
        }
        return measures.indexOfFirst { !it.completedInCycle }.let { idx ->
            if (idx < 0) 0 else idx
        }
    }

    private suspend fun saveDraftSnapshot(snapshot: QuestionnaireDraftSnapshot?) {
        val snap = snapshot ?: return
        val questionnaireId = snap.questionnaireId ?: return
        if (snap.answers.isEmpty()) return
        QuestionnairePersistence.saveDraftAnswers(
            activity,
            snap.answers,
            questionnaireId,
            snap.languageId,
            snap.questions,
        )
    }

    private suspend fun persistPauseState() {
        val sess = session ?: return
        val prog = progress ?: return
        SessionPauseStore.save(
            activity,
            SessionPauseStore.PauseState(
                clientCode = clientCode,
                sessionId = sess.sessionID,
                cycleNumber = prog.cycleNumber,
                cycleStartedAt = prog.cycleStartedAt,
                measureIndex = measureIndex.coerceIn(0, (measures.size - 1).coerceAtLeast(0)),
            ),
        )
    }

    private fun advancePastCompletedMeasures() {
        while (measureIndex < measures.size && measures[measureIndex].completedInCycle) {
            measureIndex++
        }
        CoroutineScope(Dispatchers.Main).launch {
            withContext(Dispatchers.IO) { persistPauseState() }
        }
        if (measures.isEmpty() || measures.all { it.completedInCycle } || measureIndex >= measures.size) {
            returnToMainWithCompletionDialog()
        } else {
            showMeasureInterstitial()
        }
    }

    private suspend fun loadSessionState(): Boolean {
        clientInfo = QuestionnaireCache.getAssignedClientInfos(activity)
            .firstOrNull { it.clientCode.equals(clientCode, ignoreCase = true) }
            ?: AssignedClientInfo(
                clientCode = clientCode,
                programProgress = QuestionnaireCache.getProgramProgress(activity, clientCode),
            )
        progress = clientInfo?.programProgress ?: return false
        val currentSessionId = progress?.currentSessionID ?: return false
        session = QuestionnaireCache.findProgramSession(activity, currentSessionId) ?: return false
        reloadMeasures()
        return session != null && measures.isNotEmpty()
    }

    private suspend fun refreshPendingUploadFlag() {
        pendingUpload = SessionProgramHelper.clientHasPendingUpload(activity, clientCode)
    }

    private suspend fun reloadMeasures() {
        val info = clientInfo ?: return
        val sess = session ?: return
        val prog = progress ?: return
        measures = SessionProgramHelper.measuresForSession(activity, info, sess, prog.cycleStartedAt)
    }

    private fun showMeasureInterstitial(editBeforeUpload: Boolean = false) {
        if (measures.isEmpty()) return
        if (!editBeforeUpload) {
            val firstIncomplete = measures.indexOfFirst { !it.completedInCycle }
            if (firstIncomplete >= 0) {
                measureIndex = firstIncomplete
            }
        }
        if (measureIndex >= measures.size) {
            returnToMainWithCompletionDialog()
            return
        }
        val sess = session ?: return
        val prog = progress ?: return
        val measure = measures[measureIndex]
        val completedCount = measures.count { it.completedInCycle }
        val sessionName = prog.currentSessionName ?: sess.name
        val allowMeasureSelection = editBeforeUpload

        activity.setContentView(R.layout.session_measure_interstitial)
        if (editBeforeUpload) {
            activity.findViewById<TextView>(R.id.interstitialEyebrow).text =
                hubFallback("Sitzung bearbeiten", "Edit session")
            activity.findViewById<TextView>(R.id.interstitialTitle).text = sessionName
            activity.findViewById<TextView>(R.id.interstitialSessionLabel).text =
                hubFallback("Messpunkt zum Bearbeiten wählen", "Select a measure to edit")
        } else {
            activity.findViewById<TextView>(R.id.interstitialEyebrow).text =
                hubFallback("Sitzungsmesspunkt", "Session measure")
            activity.findViewById<TextView>(R.id.interstitialTitle).text = sessionName
            activity.findViewById<TextView>(R.id.interstitialSessionLabel).text = measure.displayName
        }
        activity.findViewById<TextView>(R.id.interstitialProgress).text =
            hubFallback(
                "Messpunkt ${measureIndex + 1} von ${measures.size} · Zyklus ${prog.cycleNumber} · Sitzung ${prog.currentSessionNumber ?: sess.sessionNumber} von ${prog.totalSessions}",
                "Measure ${measureIndex + 1} of ${measures.size} · Cycle ${prog.cycleNumber} · Session ${prog.currentSessionNumber ?: sess.sessionNumber} of ${prog.totalSessions}",
            )
        activity.findViewById<TextView>(R.id.interstitialClient).text = clientCode

        val progressBar = activity.findViewById<LinearProgressIndicator>(R.id.interstitialProgressBar)
        progressBar.visibility = View.VISIBLE
        progressBar.max = measures.size
        progressBar.progress = completedCount

        activity.findViewById<TextView>(R.id.interstitialMeasureHeading).text =
            if (editBeforeUpload) {
                hubFallback("Messpunkte — zum Auswählen tippen", "Measures — tap to select")
            } else {
                hubFallback("Ihre Messpunkte in dieser Sitzung", "Your measures in this session")
            }

        val measureList = activity.findViewById<LinearLayout>(R.id.interstitialMeasureList)
        measureList.removeAllViews()
        measures.forEachIndexed { index, item ->
            measureList.addView(
                SessionMeasureUi.measureRow(
                    context = activity,
                    label = item.displayName,
                    checked = item.completedInCycle,
                    isCurrent = index == measureIndex,
                    verticalPaddingDp = 0,
                    highlightCurrentRow = true,
                    selectable = allowMeasureSelection,
                    onSelect = if (allowMeasureSelection) {
                        {
                            measureIndex = index
                            showMeasureInterstitial(editBeforeUpload = editBeforeUpload)
                        }
                    } else {
                        null
                    },
                    statusText = measureStatusLabel(
                        completed = item.completedInCycle,
                        selected = index == measureIndex,
                        editBeforeUpload = editBeforeUpload,
                    ),
                ),
            )
        }

        activity.findViewById<MaterialButton>(R.id.interstitialContinue).apply {
            text = when {
                editBeforeUpload -> hubFallback("Ausgewählten bearbeiten", "Edit selected")
                measure.completedInCycle -> hubFallback("Weiter", "Continue")
                else -> hubFallback("Messung starten", "Start measure")
            }
            setOnClickListener {
                when {
                    editBeforeUpload -> startMeasure(measure.questionnaireId)
                    measure.completedInCycle -> advancePastCompletedMeasures()
                    else -> startMeasure(measure.questionnaireId)
                }
            }
        }
        activity.findViewById<MaterialButton>(R.id.interstitialPause).apply {
            visibility = View.VISIBLE
            text = hubFallback("Sitzung pausieren", "Pause session")
            setOnClickListener { pauseSession() }
        }

        activity.findViewById<TextView>(R.id.interstitialSummary).text =
            hubFallback(
                "$completedCount von ${measures.size} Messpunkten in dieser Sitzung erledigt",
                "$completedCount of ${measures.size} measures completed in this session",
            )
        attachLanguagePicker()
    }

    private fun returnToMainWithCompletionDialog() {
        CoroutineScope(Dispatchers.Main).launch {
            withContext(Dispatchers.IO) { refreshPendingUploadFlag() }
            SessionPauseStore.clear(activity, clientCode)
            val sessionName = progress?.currentSessionName ?: session?.name.orEmpty()
            QuestionnaireLanguagePicker.detach(activity)
            activity.isInQuestionnaire = false
            onExit(
                SessionCompletionResult(
                    clientCode = clientCode,
                    sessionName = sessionName,
                    canUpload = pendingUpload,
                ),
            )
        }
    }

    private fun startMeasure(questionnaireId: String) {
        GlobalValues.LAST_CLIENT_CODE = clientCode
        GlobalValues.LOADED_CLIENT_CODE = clientCode
        activity.startQuestionnaire(QuestionnaireGeneric(questionnaireId), languageID)
    }

    private fun attachLanguagePicker() {
        QuestionnaireLanguagePicker.ensureToolbarLayout(
            activity = activity,
            languageId = languageID,
            onLanguageChanged = { newLang ->
                languageID = newLang
                AppLanguageStore.set(activity, newLang)
                if (measures.all { it.completedInCycle } && pendingUpload) {
                    showMeasureInterstitial(editBeforeUpload = true)
                } else {
                    showMeasureInterstitial()
                }
            },
        )
    }

    private fun pauseSession() {
        CoroutineScope(Dispatchers.Main).launch {
            withContext(Dispatchers.IO) { persistPauseState() }
            QuestionnaireLanguagePicker.detach(activity)
            activity.isInQuestionnaire = false
            onExit(null)
        }
    }

    fun pauseFromBackPress() {
        pauseSession()
    }

    private fun showErrorAndExit(message: String) {
        android.widget.Toast.makeText(activity, message, android.widget.Toast.LENGTH_LONG).show()
        pauseSession()
    }

    private fun measureStatusLabel(
        completed: Boolean,
        selected: Boolean,
        editBeforeUpload: Boolean,
    ): String = when {
        selected && editBeforeUpload -> hubFallback("Ausgewählt zum Bearbeiten", "Selected to edit")
        selected && !completed -> hubFallback("Als Nächstes", "Up next")
        selected -> hubFallback("Ausgewählt", "Selected")
        completed -> hubFallback("Abgeschlossen", "Complete")
        else -> hubFallback("Noch offen", "Not started")
    }

    private fun hubFallback(de: String, en: String): String =
        if (languageID == "GERMAN") de else en

    private fun dp(v: Int): Int = ViewUtils.dp(activity, v)
}
