package com.tomhempel.rbarometer.session

import android.content.Context
import com.tomhempel.rbarometer.data.CompletedQuestionnaire
import com.tomhempel.rbarometer.data.CompletedQuestionnaireRepository
import com.tomhempel.rbarometer.data.CoachScoringReviewRepository
import com.tomhempel.rbarometer.network.AssignedClientInfo
import com.tomhempel.rbarometer.network.ProgramProgress
import com.tomhempel.rbarometer.network.ProgramSessionDefinition
import com.tomhempel.rbarometer.network.QuestionnaireCache

object SessionProgramHelper {

    data class SessionMeasure(
        val questionnaireId: String,
        val displayName: String,
        val completedInCycle: Boolean,
    )

    data class ClientSessionView(
        val clientCode: String,
        val progress: ProgramProgress,
        val session: ProgramSessionDefinition?,
        val measures: List<SessionMeasure>,
        val canResumeSession: Boolean = false,
        val isSessionMeasuresComplete: Boolean = false,
        val hasPendingUpload: Boolean = false,
    ) {
        val pendingMeasureCount: Int get() = measures.count { !it.completedInCycle }
        val canStart: Boolean get() = session != null && !progress.programComplete && pendingMeasureCount > 0
        /** Session can be opened for start, resume, or edit-before-upload. */
        val canEnterSession: Boolean get() = when {
            progress.programComplete -> false
            session == null -> false
            isSessionMeasuresComplete && !hasPendingUpload -> false
            else -> canStart || canResumeSession || measures.any { it.completedInCycle } ||
                (isSessionMeasuresComplete && hasPendingUpload)
        }
    }

    enum class SessionDisplayBucket {
        READY,
        IN_PROGRESS,
        WAITING_UPLOAD,
        COMPLETE,
    }

    fun sessionDisplayBucket(view: ClientSessionView): SessionDisplayBucket = when {
        view.progress.programComplete -> SessionDisplayBucket.COMPLETE
        view.isSessionMeasuresComplete && view.hasPendingUpload -> SessionDisplayBucket.WAITING_UPLOAD
        view.isSessionMeasuresComplete -> SessionDisplayBucket.COMPLETE
        view.canResumeSession || view.measures.any { it.completedInCycle } -> SessionDisplayBucket.IN_PROGRESS
        view.canStart -> SessionDisplayBucket.READY
        view.session != null -> SessionDisplayBucket.IN_PROGRESS
        else -> SessionDisplayBucket.COMPLETE
    }

    suspend fun clientHasPendingUpload(context: Context, clientCode: String): Boolean {
        val info = clientInfo(context, clientCode)
        val progress = info.programProgress ?: return false
        val session = progress.currentSessionID?.let { QuestionnaireCache.findProgramSession(context, it) }
            ?: return false
        val measures = measuresForSession(context, info, session, progress.cycleStartedAt)
        return sessionHasPendingUpload(clientCode, measures)
    }

    /** Raw pending rows for a completed session — must not call upload-ready filters (avoids recursion). */
    private suspend fun sessionHasPendingUpload(
        clientCode: String,
        measures: List<SessionMeasure>,
    ): Boolean {
        if (measures.isEmpty() || !measures.all { it.completedInCycle }) return false
        val pendingQ = CompletedQuestionnaireRepository.getPendingUploadForClient(clientCode)
        if (pendingQ.any { entry ->
                measures.any { m -> questionnaireIdsMatch(m.questionnaireId, entry.questionnaireId) }
            }
        ) {
            return true
        }
        return CoachScoringReviewRepository.getAllForClient(clientCode).any { it.isPendingUpload }
    }

    private fun questionnaireIdsMatch(a: String, b: String): Boolean {
        val na = normalizeQuestionnaireId(a)
        val nb = normalizeQuestionnaireId(b)
        if (na.isBlank() || nb.isBlank()) return false
        return na == nb || na.contains(nb) || nb.contains(na)
    }

    suspend fun buildClientSessionViews(context: Context): List<ClientSessionView> {
        val questionnaireNames = QuestionnaireCache.getQuestionnaireList(context)
        val clientInfos = QuestionnaireCache.getAssignedClientInfos(context)
        if (clientInfos.isNotEmpty()) {
            return clientInfos.mapNotNull { info ->
                buildClientSessionView(context, info, questionnaireNames)
            }.sortedBy { it.clientCode }
        }
        return QuestionnaireCache.getAssignedClients(context).mapNotNull { code ->
            buildClientSessionView(
                context,
                AssignedClientInfo(clientCode = code, programProgress = QuestionnaireCache.getProgramProgress(context, code)),
                questionnaireNames,
            )
        }.sortedBy { it.clientCode }
    }

    suspend fun buildClientSessionView(
        context: Context,
        clientInfo: AssignedClientInfo,
        questionnaireNames: List<com.tomhempel.rbarometer.network.QuestionnaireListItem> =
            QuestionnaireCache.getQuestionnaireList(context),
    ): ClientSessionView? {
        val progress = clientInfo.programProgress ?: return null
        val session = progress.currentSessionID?.let { QuestionnaireCache.findProgramSession(context, it) }
        val localCompleted = CompletedQuestionnaireRepository.getAllForClient(clientInfo.clientCode)
            .associateBy { it.questionnaireId.lowercase() }
        val measures = if (session != null) {
            measuresForSession(context, clientInfo, session, progress.cycleStartedAt, localCompleted, questionnaireNames)
        } else {
            emptyList()
        }
        val isSessionMeasuresComplete = measures.isNotEmpty() && measures.all { it.completedInCycle }
        val hasPendingUpload = if (isSessionMeasuresComplete) {
            sessionHasPendingUpload(clientInfo.clientCode, measures)
        } else {
            false
        }
        return ClientSessionView(
            clientCode = clientInfo.clientCode,
            progress = progress,
            session = session,
            measures = measures,
            canResumeSession = SessionPauseStore.matchesCurrentProgram(
                context = context,
                clientCode = clientInfo.clientCode,
                sessionId = session?.sessionID,
                cycleNumber = progress.cycleNumber,
                cycleStartedAt = progress.cycleStartedAt,
            ),
            isSessionMeasuresComplete = isSessionMeasuresComplete,
            hasPendingUpload = hasPendingUpload,
        )
    }

    suspend fun measuresForSession(
        context: Context,
        clientInfo: AssignedClientInfo,
        session: ProgramSessionDefinition,
        cycleStartedAtSec: Long,
    ): List<SessionMeasure> {
        val localCompleted = CompletedQuestionnaireRepository.getAllForClient(clientInfo.clientCode)
            .associateBy { it.questionnaireId.lowercase() }
        return measuresForSession(
            context,
            clientInfo,
            session,
            cycleStartedAtSec,
            localCompleted,
            QuestionnaireCache.getQuestionnaireList(context),
        )
    }

    private fun measuresForSession(
        context: Context,
        clientInfo: AssignedClientInfo,
        session: ProgramSessionDefinition,
        cycleStartedAtSec: Long,
        localCompleted: Map<String, CompletedQuestionnaire>,
        questionnaireNames: List<com.tomhempel.rbarometer.network.QuestionnaireListItem>,
    ): List<SessionMeasure> =
        session.questionnaires.sortedBy { it.orderIndex }.map { member ->
            SessionMeasure(
                questionnaireId = member.questionnaireID,
                displayName = questionnaireDisplayName(context, member.questionnaireID, questionnaireNames),
                completedInCycle = isMeasureCompleteInCycle(
                    clientInfo,
                    member.questionnaireID,
                    cycleStartedAtSec,
                    findLocalCompletion(localCompleted, member.questionnaireID),
                ),
            )
        }

    suspend fun isMeasureCompleteInCycle(
        clientInfo: AssignedClientInfo,
        questionnaireId: String,
        cycleStartedAtSec: Long,
    ): Boolean {
        val localCompleted = CompletedQuestionnaireRepository.getAllForClient(clientInfo.clientCode)
            .associateBy { it.questionnaireId.lowercase() }
        val local = findLocalCompletion(localCompleted, questionnaireId)
        return isMeasureCompleteInCycle(clientInfo, questionnaireId, cycleStartedAtSec, local)
    }

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

    private fun findLocalCompletion(
        localCompleted: Map<String, CompletedQuestionnaire>,
        questionnaireId: String,
    ): CompletedQuestionnaire? {
        localCompleted[questionnaireId.lowercase()]?.let { return it }
        val target = normalizeQuestionnaireId(questionnaireId)
        if (target.isBlank()) return null
        return localCompleted.entries.firstOrNull { (key, _) ->
            val candidate = normalizeQuestionnaireId(key)
            candidate == target || candidate.contains(target) || target.contains(candidate)
        }?.value
    }

    private fun isMeasureCompleteInCycle(
        clientInfo: AssignedClientInfo,
        questionnaireId: String,
        cycleStartedAtSec: Long,
        local: CompletedQuestionnaire?,
    ): Boolean {
        if (local?.isDone == true) {
            val tsSec = local.timestamp / 1000L
            if (cycleStartedAtSec <= 0L || tsSec > cycleStartedAtSec) {
                return true
            }
        }
        clientInfo.completedQuestionnaires
            .firstOrNull { it.questionnaireID.equals(questionnaireId, ignoreCase = true) }
            ?.let { server ->
                if (cycleStartedAtSec <= 0L || server.completedAt > cycleStartedAtSec) {
                    return true
                }
            }
        return false
    }

    fun questionnaireDisplayName(
        context: Context,
        questionnaireId: String,
        questionnaireNames: List<com.tomhempel.rbarometer.network.QuestionnaireListItem> =
            QuestionnaireCache.getQuestionnaireList(context),
    ): String {
        val fromList = questionnaireNames
            .firstOrNull { it.id.equals(questionnaireId, ignoreCase = true) }
            ?.name
            ?.takeIf { it.isNotBlank() }
        return fromList ?: questionnaireId
    }

    private fun clientInfo(context: Context, clientCode: String): AssignedClientInfo =
        QuestionnaireCache.getAssignedClientInfos(context)
            .firstOrNull { it.clientCode.equals(clientCode, ignoreCase = true) }
            ?: AssignedClientInfo(
                clientCode = clientCode,
                programProgress = QuestionnaireCache.getProgramProgress(context, clientCode),
            )

    /** True when the client's current program session has all measures completed (or no active session). */
    suspend fun isClientSessionReadyForUpload(context: Context, clientCode: String): Boolean {
        val info = clientInfo(context, clientCode)
        val progress = info.programProgress ?: return true
        if (progress.programComplete) return true
        val session = progress.currentSessionID?.let { QuestionnaireCache.findProgramSession(context, it) }
            ?: return true
        val measures = measuresForSession(context, info, session, progress.cycleStartedAt)
        return measures.isEmpty() || measures.all { it.completedInCycle }
    }

    suspend fun filterUploadReadyQuestionnaires(
        context: Context,
        pending: List<CompletedQuestionnaire>,
    ): List<CompletedQuestionnaire> {
        if (pending.isEmpty()) return pending
        val readyByClient = mutableMapOf<String, Boolean>()
        return pending.filter { entry ->
            readyByClient.getOrPut(entry.clientCode.lowercase()) {
                isClientSessionReadyForUpload(context, entry.clientCode)
            }
        }
    }

    suspend fun filterUploadReadyScoringReviews(
        context: Context,
        pending: List<com.tomhempel.rbarometer.data.CoachScoringReview>,
    ): List<com.tomhempel.rbarometer.data.CoachScoringReview> {
        if (pending.isEmpty()) return pending
        val readyByClient = mutableMapOf<String, Boolean>()
        return pending.filter { review ->
            readyByClient.getOrPut(review.clientCode.lowercase()) {
                isClientSessionReadyForUpload(context, review.clientCode)
            }
        }
    }
}
