package com.tomhempel.rbarometer.ui

import android.content.res.ColorStateList
import android.graphics.Typeface
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.tomhempel.rbarometer.MainActivity
import com.tomhempel.rbarometer.R
import com.tomhempel.rbarometer.data.CoachScoringReviewRepository
import com.tomhempel.rbarometer.network.ClientScoringReview
import com.tomhempel.rbarometer.network.QuestionnaireApiClient
import com.tomhempel.rbarometer.network.QuestionnaireCache
import com.tomhempel.rbarometer.network.ScoringReviewProfile
import com.tomhempel.rbarometer.notification.UploadReminderScheduler
import com.tomhempel.rbarometer.scoring.LocalScoringReviewBuilder
import com.tomhempel.rbarometer.questionnaire.AppLanguages
import com.tomhempel.rbarometer.ui.localization.UiStrings
import com.tomhempel.rbarometer.utils.ScreenLoading
import com.tomhempel.rbarometer.utils.ViewUtils
import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * Coach review of locally computed scoring profile totals and bands (same rules as server).
 */
class ReviewScoresHandler(
    private val activity: MainActivity,
    private val token: String,
    private val languageIDProvider: () -> String,
    private val clientCodeFilter: String? = null,
    private val onLanguageChanged: (String) -> Unit = {},
    private val onUploadRequested: (() -> Unit)? = null,
    private val onClose: () -> Unit,
) {
    private val uiScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    private var reviewLanguageId: String = languageIDProvider()
    private var langSpinner: Spinner? = null
    private var suppressLangCallback = false
    private var loadJob: Job? = null
    private var active = false
    private var clients: List<ClientScoringReview> = emptyList()
    private lateinit var reviewCompletePanel: View
    private lateinit var reviewCompleteTitle: TextView
    private lateinit var reviewCompleteBody: TextView
    private lateinit var reviewCompleteUploadButton: MaterialButton
    private val singleClientMode: Boolean
        get() = !clientCodeFilter.isNullOrBlank()

    sealed class ListItem {
        data class ClientHeader(val clientCode: String) : ListItem()
        data class Profile(
            val clientCode: String,
            val profile: ScoringReviewProfile,
        ) : ListItem()
    }

    fun show() {
        active = true
        activity.setContentView(R.layout.review_scores_screen)
        activity.setBackPressInterceptor {
            close()
            true
        }
        reviewLanguageId = languageIDProvider()

        val titleTv = requireView<TextView>(R.id.titleReviewScores)
        val subtitleTv = requireView<TextView>(R.id.subtitleReviewScores)
        val emptyView = requireView<TextView>(R.id.emptyViewReview)
        val listLoading = requireView<View>(R.id.reviewListLoading)
        val recycler = requireView<RecyclerView>(R.id.reviewListRecycler)
        val backButton = requireView<Button>(R.id.backButtonReview)
        langSpinner = requireView(R.id.reviewLangSpinner)
        reviewCompletePanel = requireView(R.id.reviewCompletePanel)
        reviewCompleteTitle = requireView(R.id.reviewCompleteTitle)
        reviewCompleteBody = requireView(R.id.reviewCompleteBody)
        reviewCompleteUploadButton = requireView(R.id.reviewCompleteUploadButton)

        titleTv.text = td("review_scores")
        subtitleTv.text = subtitleText()
        backButton.text = td("previous")
        bindCompletionPanel()

        setupLanguageSpinner {
            titleTv.text = td("review_scores")
            subtitleTv.text = subtitleText()
            backButton.text = td("previous")
            bindCompletionPanel()
            bindList(recycler, emptyView)
        }

        backButton.setOnClickListener { close() }

        val adapter = ReviewListAdapter(
            onAgree = { clientCode, profile -> submitReview(clientCode, profile, profile.calculatedBand) },
            onSetBand = { clientCode, profile, band -> submitReview(clientCode, profile, band) },
            labelProvider = ::labels,
            bandLabel = ::bandLabel,
            bandColors = ::bandChipColors,
        )
        recycler.layoutManager = LinearLayoutManager(activity)
        recycler.adapter = adapter

        loadJob?.cancel()
        ScreenLoading.setVisible(listLoading, true)
        recycler.visibility = View.GONE
        emptyView.visibility = View.GONE

        loadJob = uiScope.launch {
            val loaded = withContext(Dispatchers.IO) { loadReviewData() }
            if (!active) return@launch
            clients = loaded
            ScreenLoading.setVisible(listLoading, false)
            bindList(recycler, emptyView)
        }
    }

    private fun bindList(recycler: RecyclerView, emptyView: TextView) {
        val items = flattenClients(clients)
        val adapter = recycler.adapter as? ReviewListAdapter ?: return
        adapter.submit(items, labels())
        if (items.isEmpty()) {
            recycler.visibility = View.GONE
            emptyView.visibility = View.VISIBLE
            emptyView.text = td("review_scores_empty")
        } else {
            recycler.visibility = View.VISIBLE
            emptyView.visibility = View.GONE
        }
        bindCompletionPanel()
    }

    private fun bindCompletionPanel() {
        if (!::reviewCompletePanel.isInitialized) return
        val profiles = clients.flatMap { it.profiles }
        val hasProfiles = profiles.isNotEmpty()
        val allReviewed = hasProfiles && profiles.all { !it.pendingReview && !it.coachBand.isNullOrBlank() }
        reviewCompletePanel.visibility = if (allReviewed) View.VISIBLE else View.GONE
        if (allReviewed) {
            reviewCompleteTitle.text = td("review_scores_complete_title")
            reviewCompleteBody.text = td("review_scores_complete_body")
            if (onUploadRequested == null) {
                reviewCompleteUploadButton.visibility = View.GONE
                reviewCompleteUploadButton.setOnClickListener(null)
            } else {
                reviewCompleteUploadButton.visibility = View.VISIBLE
                reviewCompleteUploadButton.text = td("upload")
                reviewCompleteUploadButton.setOnClickListener {
                    requestUploadFromCompletePanel()
                }
            }
        }
    }

    private fun flattenClients(source: List<ClientScoringReview>): List<ListItem> {
        val out = mutableListOf<ListItem>()
        source.forEach { client ->
            if (client.profiles.isEmpty()) return@forEach
            if (!singleClientMode) {
                out += ListItem.ClientHeader(client.clientCode)
            }
            client.profiles.forEach { profile ->
                out += ListItem.Profile(client.clientCode, profile)
            }
        }
        return out
    }

    private suspend fun loadReviewData(): List<ClientScoringReview> {
        var profiles = QuestionnaireCache.getScoringProfiles(activity)
        if (profiles.isEmpty()) {
            profiles = runCatching { QuestionnaireApiClient.getScoringProfiles(token) }
                .getOrDefault(emptyList())
            if (profiles.isNotEmpty()) {
                QuestionnaireCache.saveScoringProfiles(activity, profiles)
            }
        }

        val clientCodes = clientCodeFilter
            ?.trim()
            ?.takeIf { it.isNotBlank() }
            ?.let(::listOf)
            ?: QuestionnaireCache.getAssignedClients(activity)
        val computed = LocalScoringReviewBuilder.build(activity, clientCodes, profiles)

        val coachByClient = runCatching { QuestionnaireApiClient.getScoringReview(token, clientCodeFilter) }
            .getOrDefault(emptyList())
            .associate { review ->
                review.clientCode to review.profiles.associateBy { it.profileID }
            }

        val localByClient = clientCodes.associateWith { code ->
            CoachScoringReviewRepository.getAllForClient(code).associateBy { it.profileID }
        }

        return computed.map { client ->
            val coachProfiles = coachByClient[client.clientCode].orEmpty()
            val localProfiles = localByClient[client.clientCode].orEmpty()
            client.copy(
                profiles = client.profiles.map { profile ->
                    val local = localProfiles[profile.profileID]
                    val server = coachProfiles[profile.profileID]
                    when {
                        local != null -> profile.copy(
                            coachBand = local.coachBand,
                            calculatedBand = profile.calculatedBand,
                            weightedTotal = profile.weightedTotal,
                            pendingReview = false,
                            pendingUpload = local.isPendingUpload,
                        )
                        server?.coachBand != null -> profile.copy(
                            coachBand = server.coachBand,
                            pendingReview = false,
                            pendingUpload = false,
                        )
                        else -> profile.copy(
                            pendingReview = true,
                            pendingUpload = false,
                        )
                    }
                },
            )
        }
    }

    private fun subtitleText(): String {
        val code = clientCodeFilter?.trim().orEmpty()
        if (code.isBlank()) return td("review_scores_subtitle")
        return "${td("client")}: $code"
    }

    private fun submitReview(
        clientCode: String,
        profile: ScoringReviewProfile,
        coachBand: String,
    ) {
        uiScope.launch {
            withContext(Dispatchers.IO) {
                CoachScoringReviewRepository.saveDecision(
                    clientCode = clientCode,
                    profileID = profile.profileID,
                    profileName = profile.name,
                    coachBand = coachBand,
                    calculatedBand = profile.calculatedBand,
                    weightedTotal = profile.weightedTotal,
                )
            }
            UploadReminderScheduler.schedule(activity)
            clients = clients.map { client ->
                if (client.clientCode != clientCode) client
                else client.copy(
                    profiles = client.profiles.map { p ->
                        if (p.profileID != profile.profileID) {
                            p
                        } else {
                            p.copy(
                                coachBand = coachBand,
                                pendingReview = false,
                                pendingUpload = true,
                            )
                        }
                    },
                )
            }
            val recycler = activity.findViewById<RecyclerView>(R.id.reviewListRecycler)
            val emptyView = activity.findViewById<TextView>(R.id.emptyViewReview)
            bindList(recycler, emptyView)
            bindCompletionPanel()
            Toast.makeText(activity, td("review_scores_saved_pending"), Toast.LENGTH_SHORT).show()
        }
    }

    private data class ReviewLabels(
        val clientPrefix: String,
        val total: String,
        val calculated: String,
        val coach: String,
        val agree: String,
        val setCategory: String,
        val pending: String,
        val pendingUpload: String,
        val green: String,
        val yellow: String,
        val red: String,
    )

    private fun labels(): ReviewLabels = ReviewLabels(
        clientPrefix = td("client"),
        total = td("review_scores_total"),
        calculated = td("review_scores_calculated_category"),
        coach = td("review_scores_coach_category"),
        agree = td("review_scores_agree"),
        setCategory = td("review_scores_set_category"),
        pending = td("not_done"),
        pendingUpload = td("review_scores_pending_upload"),
        green = td("category_green"),
        yellow = td("category_yellow"),
        red = td("category_red"),
    )

    private fun bandLabel(band: String?): String = when (band) {
        "green" -> td("category_green")
        "yellow" -> td("category_yellow")
        "red" -> td("category_red")
        else -> td("not_done")
    }

    private data class BandChipColors(val bg: Int, val fg: Int)

    private fun bandChipColors(band: String?): BandChipColors {
        val (bgRes, fgRes) = when (band) {
            "green" -> R.color.score_low_emphasis to R.color.score_low_text
            "yellow" -> R.color.score_mid_emphasis to R.color.score_mid_text
            "red" -> R.color.score_high_emphasis to R.color.score_high_text
            else -> R.color.score_none_emphasis to R.color.score_none_text
        }
        return BandChipColors(
            bg = ContextCompat.getColor(activity, bgRes),
            fg = ContextCompat.getColor(activity, fgRes),
        )
    }

    private fun setupLanguageSpinner(onChanged: () -> Unit) {
        val spinner = langSpinner ?: return
        fun bindAdapter() {
            val labels = AppLanguages.spinnerLabels(reviewLanguageId)
            val selected = labels.getOrNull(AppLanguages.indexOf(reviewLanguageId))
            ViewUtils.setupLanguagePickerSpinner(activity, spinner, labels, selected)
        }
        bindAdapter()
        spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                if (suppressLangCallback) return
                val newLang = AppLanguages.ids[position]
                if (newLang == reviewLanguageId) return
                reviewLanguageId = newLang
                AppLanguageStore.set(activity, reviewLanguageId)
                onLanguageChanged(reviewLanguageId)
                suppressLangCallback = true
                bindAdapter()
                spinner.setSelection(AppLanguages.indexOf(reviewLanguageId), false)
                suppressLangCallback = false
                onChanged()
            }
            override fun onNothingSelected(parent: AdapterView<*>?) {}
        }
        suppressLangCallback = true
        spinner.setSelection(AppLanguages.indexOf(reviewLanguageId), false)
        suppressLangCallback = false
    }

    private fun close() {
        active = false
        loadJob?.cancel()
        loadJob = null
        activity.setBackPressInterceptor(null)
        onClose()
    }

    private fun requestUploadFromCompletePanel() {
        val callback = onUploadRequested
        if (callback == null) {
            close()
            return
        }
        active = false
        loadJob?.cancel()
        loadJob = null
        activity.setBackPressInterceptor(null)
        callback()
    }

    private fun td(key: String): String = UiStrings.tDisplay(reviewLanguageId, key, key)

    companion object {
        suspend fun hasScoresForClient(
            activity: MainActivity,
            token: String,
            clientCode: String,
        ): Boolean {
            val code = clientCode.trim()
            if (code.isBlank()) return false

            var profiles = QuestionnaireCache.getScoringProfiles(activity)
            if (profiles.isEmpty()) {
                profiles = runCatching { QuestionnaireApiClient.getScoringProfiles(token) }
                    .getOrDefault(emptyList())
                if (profiles.isNotEmpty()) {
                    QuestionnaireCache.saveScoringProfiles(activity, profiles)
                }
            }

            return LocalScoringReviewBuilder
                .build(activity, listOf(code), profiles)
                .any { it.profiles.isNotEmpty() }
        }

        suspend fun hasPendingReviewsForClient(
            activity: MainActivity,
            token: String,
            clientCode: String,
        ): Boolean {
            val code = clientCode.trim()
            if (code.isBlank()) return false

            var profiles = QuestionnaireCache.getScoringProfiles(activity)
            if (profiles.isEmpty()) {
                profiles = runCatching { QuestionnaireApiClient.getScoringProfiles(token) }
                    .getOrDefault(emptyList())
                if (profiles.isNotEmpty()) {
                    QuestionnaireCache.saveScoringProfiles(activity, profiles)
                }
            }

            val computed = LocalScoringReviewBuilder
                .build(activity, listOf(code), profiles)
                .firstOrNull()
                ?.profiles
                .orEmpty()
            if (computed.isEmpty()) return false

            val localByProfile = CoachScoringReviewRepository
                .getAllForClient(code)
                .associateBy { it.profileID }
            val serverByProfile = runCatching {
                QuestionnaireApiClient.getScoringReview(token, code)
            }
                .getOrDefault(emptyList())
                .firstOrNull { it.clientCode.equals(code, ignoreCase = true) }
                ?.profiles
                ?.associateBy { it.profileID }
                .orEmpty()

            return computed.any { profile ->
                localByProfile[profile.profileID] == null &&
                    serverByProfile[profile.profileID]?.coachBand.isNullOrBlank()
            }
        }
    }

    @Suppress("UNCHECKED_CAST")
    private fun <T : View> requireView(id: Int): T {
        return activity.findViewById(id)
            ?: throw IllegalStateException("Missing view $id")
    }

    private class ReviewListAdapter(
        private val onAgree: (String, ScoringReviewProfile) -> Unit,
        private val onSetBand: (String, ScoringReviewProfile, String) -> Unit,
        private val labelProvider: () -> ReviewLabels,
        private val bandLabel: (String?) -> String,
        private val bandColors: (String?) -> BandChipColors,
    ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

        private var items: List<ListItem> = emptyList()
        private var labels: ReviewLabels = ReviewLabels("", "", "", "", "", "", "", "", "", "", "")

        fun submit(list: List<ListItem>, labels: ReviewLabels) {
            items = list
            this.labels = labels
            notifyDataSetChanged()
        }

        override fun getItemViewType(position: Int): Int =
            if (items[position] is ListItem.ClientHeader) 0 else 1

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            val inflater = LayoutInflater.from(parent.context)
            return if (viewType == 0) {
                HeaderHolder(inflater.inflate(R.layout.item_upload_client_header, parent, false))
            } else {
                ProfileHolder(inflater.inflate(R.layout.review_score_profile_item, parent, false))
            }
        }

        override fun getItemCount(): Int = items.size

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            when (val item = items[position]) {
                is ListItem.ClientHeader -> {
                    (holder as HeaderHolder).text.text = "${labels.clientPrefix}: ${item.clientCode}"
                }
                is ListItem.Profile -> bindProfile(holder as ProfileHolder, item)
            }
        }

        private fun bindProfile(holder: ProfileHolder, item: ListItem.Profile) {
            val profile = item.profile
            holder.name.text = profile.name
            holder.total.text = "${labels.total}: ${profile.weightedTotal}"
            holder.ranges.text = rangeText(profile)
            holder.calculatedLabel.text = labels.calculated
            holder.coachLabel.text = labels.coach
            holder.setCategoryLabel.text = labels.setCategory
            holder.agreeButton.text = labels.agree
            holder.greenButton.text = labels.green
            holder.yellowButton.text = labels.yellow
            holder.redButton.text = labels.red
            styleBandButton(holder.greenButton, "green")
            styleBandButton(holder.yellowButton, "yellow")
            styleBandButton(holder.redButton, "red")

            styleBandChip(holder.calculatedBand, profile.calculatedBand)
            val coachBand = profile.coachBand
            when {
                profile.pendingReview -> {
                    styleBandChip(holder.coachBand, null)
                    holder.coachBand.text = labels.pending
                }
                profile.pendingUpload -> {
                    styleBandChip(holder.coachBand, coachBand)
                    holder.coachBand.text = "${bandLabel(coachBand)} · ${labels.pendingUpload}"
                }
                else -> styleBandChip(holder.coachBand, coachBand)
            }

            holder.agreeButton.setOnClickListener { onAgree(item.clientCode, profile) }
            holder.greenButton.setOnClickListener { onSetBand(item.clientCode, profile, "green") }
            holder.yellowButton.setOnClickListener { onSetBand(item.clientCode, profile, "yellow") }
            holder.redButton.setOnClickListener { onSetBand(item.clientCode, profile, "red") }
        }

        private fun styleBandChip(chip: TextView, band: String?) {
            val colors = bandColors(band)
            chip.text = bandLabel(band)
            chip.setBackgroundColor(colors.bg)
            chip.setTextColor(colors.fg)
            chip.setTypeface(chip.typeface, Typeface.BOLD)
            chip.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
        }

        private fun styleBandButton(button: MaterialButton, band: String) {
            val colors = bandColors(band)
            button.backgroundTintList = ColorStateList.valueOf(colors.bg)
            button.setTextColor(colors.fg)
            button.strokeColor = ColorStateList.valueOf(colors.fg)
            button.strokeWidth = 1
        }

        private fun rangeText(profile: ScoringReviewProfile): String =
            "${labels.green}: ${profile.greenMin}-${profile.greenMax} · " +
                "${labels.yellow}: ${profile.yellowMin}-${profile.yellowMax} · " +
                "${labels.red}: ${profile.redMin}+"

        private class HeaderHolder(view: View) : RecyclerView.ViewHolder(view) {
            val text: TextView = view.findViewById(R.id.clientHeaderText)
        }

        private class ProfileHolder(view: View) : RecyclerView.ViewHolder(view) {
            val name: TextView = view.findViewById(R.id.profileNameText)
            val total: TextView = view.findViewById(R.id.profileTotalText)
            val ranges: TextView = view.findViewById(R.id.profileRangesText)
            val calculatedLabel: TextView = view.findViewById(R.id.calculatedLabelText)
            val coachLabel: TextView = view.findViewById(R.id.coachLabelText)
            val calculatedBand: TextView = view.findViewById(R.id.calculatedBandChip)
            val coachBand: TextView = view.findViewById(R.id.coachBandChip)
            val agreeButton: MaterialButton = view.findViewById(R.id.agreeButton)
            val setCategoryLabel: TextView = view.findViewById(R.id.setCategoryLabel)
            val greenButton: MaterialButton = view.findViewById(R.id.greenButton)
            val yellowButton: MaterialButton = view.findViewById(R.id.yellowButton)
            val redButton: MaterialButton = view.findViewById(R.id.redButton)
        }
    }
}
