package com.tomhempel.rbarometer.utils

import android.app.Activity
import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat
import com.tomhempel.rbarometer.R
import com.tomhempel.rbarometer.network.SyncPhase
import com.tomhempel.rbarometer.network.SyncProgress
import com.google.android.material.progressindicator.LinearProgressIndicator

/**
 * Full-screen sync overlay with phase-based status text (no counts) and restrained motion.
 */
class SyncProgressOverlay(
    private val activity: Activity,
    initialLocale: SyncProgressTexts.Locale = SyncProgressTexts.Locale.GERMAN,
) {

    private var locale: SyncProgressTexts.Locale = initialLocale
    private var root: View? = null
    private var panel: View? = null
    private var titleView: TextView? = null
    private var statusView: TextView? = null
    private var subtitleView: TextView? = null
    private var phaseIcon: ImageView? = null
    private var bar: LinearProgressIndicator? = null
    private var checkpointList: LinearLayout? = null
    private val checkpoints = mutableListOf<CheckpointViews>()
    private var lastProgress: SyncProgress? = null
    private var hideRunnable: Runnable? = null

    private val checkpointPhases = listOf(
        SyncPhase.PREPARING,
        SyncPhase.DOWNLOADING_QUESTIONNAIRES,
        SyncPhase.DOWNLOADING_CLIENT_INFO,
        SyncPhase.DOWNLOADING_ANSWERS,
        SyncPhase.PROCESSING,
        SyncPhase.SECURELY_SAVING,
        SyncPhase.FINISHED,
    )

    private data class CheckpointViews(
        val phase: SyncPhase,
        val dot: View,
    )

    fun setLocale(locale: SyncProgressTexts.Locale) {
        this.locale = locale
        applyTexts()
    }

    fun show() {
        ensureInflated()
        hideRunnable?.let { root?.removeCallbacks(it) }
        hideRunnable = null
        applyTexts()
        root?.bringToFront()
        root?.visibility = View.VISIBLE
        root?.animate()?.cancel()
        root?.alpha = 0f
        root?.animate()
            ?.alpha(1f)
            ?.setDuration(240L)
            ?.start()
        panel?.let { card ->
            card.clearAnimation()
            card.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.sync_overlay_enter))
        }
        applyProgressState(lastProgress ?: SyncProgress(SyncPhase.PREPARING))
    }

    fun update(progress: SyncProgress) {
        val phaseChanged = lastProgress == null || lastProgress?.phase != progress.phase
        lastProgress = progress
        ensureInflated()
        applyTexts(animate = phaseChanged)
        applyProgressState(progress, animateIcon = phaseChanged)
    }

    fun hide() {
        bar?.hide()
        val overlay = root ?: return
        hideRunnable?.let { overlay.removeCallbacks(it) }
        overlay.animate().cancel()
        panel?.clearAnimation()
        overlay.animate()
            .alpha(0f)
            .setDuration(220L)
            .withEndAction {
                if (!overlay.isVisible) return@withEndAction
                overlay.visibility = View.GONE
                overlay.alpha = 1f
            }
            .start()
        hideRunnable = Runnable {
            overlay.visibility = View.GONE
            overlay.alpha = 1f
        }.also { overlay.postDelayed(it, 260L) }
        lastProgress = null
    }

    private fun applyTexts(animate: Boolean = false) {
        titleView?.text = SyncProgressTexts.title(locale)
        val progress = lastProgress ?: SyncProgress(SyncPhase.PREPARING)
        statusView?.text = SyncProgressTexts.status(progress.phase, locale)
        subtitleView?.text = SyncProgressTexts.subtitle(progress.phase, locale)
        if (animate) {
            val fade = AnimationUtils.loadAnimation(activity, R.anim.sync_text_fade)
            statusView?.startAnimation(fade)
            subtitleView?.startAnimation(fade)
        }
    }

    private fun applyProgressState(progress: SyncProgress, animateIcon: Boolean = false) {
        val finished = progress.phase == SyncPhase.FINISHED
        phaseIcon?.setImageResource(SyncPhaseIcons.iconRes(progress.phase))
        val tint = ContextCompat.getColor(
            activity,
            if (finished) R.color.brand_status_online else R.color.brand_purple,
        )
        phaseIcon?.let { ImageViewCompat.setImageTintList(it, android.content.res.ColorStateList.valueOf(tint)) }
        if (animateIcon) {
            phaseIcon?.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.sync_text_fade))
        }
        bar?.visibility = View.VISIBLE
        bar?.show()
        updateCheckpoints(progress.phase)
    }

    private fun ensureInflated() {
        if (root != null) return
        val decor = activity.window.decorView as? ViewGroup ?: return
        val overlay = LayoutInflater.from(activity)
            .inflate(R.layout.view_sync_progress_overlay, decor, false)
        decor.addView(
            overlay,
            ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT,
            ),
        )
        root = overlay
        panel = overlay.findViewById(R.id.syncProgressPanel)
        titleView = overlay.findViewById(R.id.syncProgressTitle)
        statusView = overlay.findViewById(R.id.syncProgressStatus)
        subtitleView = overlay.findViewById(R.id.syncProgressSubtitle)
        phaseIcon = overlay.findViewById(R.id.syncProgressPhaseIcon)
        bar = overlay.findViewById(R.id.syncProgressBar)
        checkpointList = overlay.findViewById(R.id.syncCheckpointList)
        buildCheckpointList()
        bar?.max = checkpointPhases.lastIndex
    }

    private fun buildCheckpointList() {
        val list = checkpointList ?: return
        if (checkpoints.isNotEmpty()) return
        list.removeAllViews()

        checkpointPhases.forEachIndexed { index, phase ->
            val cell = android.widget.FrameLayout(activity).apply {
                layoutParams = LinearLayout.LayoutParams(
                    0,
                    LinearLayout.LayoutParams.MATCH_PARENT,
                    1f,
                )
            }
            val dot = View(activity).apply {
                layoutParams = android.widget.FrameLayout.LayoutParams(dp(14), dp(14)).apply {
                    gravity = android.view.Gravity.CENTER
                }
            }
            cell.addView(dot)
            list.addView(cell)
            checkpoints += CheckpointViews(phase, dot)
        }
    }

    private fun updateCheckpoints(currentPhase: SyncPhase) {
        if (checkpoints.isEmpty()) return
        val currentIndex = checkpointPhases.indexOf(currentPhase).coerceAtLeast(0)
        val completedColor = ContextCompat.getColor(activity, R.color.brand_status_online)
        val activeColor = ContextCompat.getColor(activity, R.color.brand_purple)
        val pendingColor = ContextCompat.getColor(activity, R.color.brand_surface)
        val pendingStroke = ContextCompat.getColor(activity, R.color.brand_loading_track)
        bar?.setProgressCompat(currentIndex, true)

        checkpoints.forEachIndexed { index, checkpoint ->
            val isComplete = index < currentIndex || currentPhase == SyncPhase.FINISHED
            val isActive = index == currentIndex && currentPhase != SyncPhase.FINISHED
            checkpoint.dot.background = circle(
                fillColor = when {
                    isComplete -> completedColor
                    isActive -> activeColor
                    else -> pendingColor
                },
                strokeColor = if (isComplete || isActive) activeColor else pendingStroke,
            )
        }
    }

    private fun circle(fillColor: Int, strokeColor: Int): GradientDrawable =
        GradientDrawable().apply {
            shape = GradientDrawable.OVAL
            setColor(fillColor)
            setStroke(dp(2), strokeColor)
        }

    private fun dp(value: Int): Int =
        (value * activity.resources.displayMetrics.density).toInt()
}
