package partisan.weforge.xyz.pulse

import android.Manifest
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.Rect
import android.media.AudioManager
import android.net.Uri
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.view.ContextThemeWrapper
import android.widget.TextView
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.os.Handler
import android.os.Looper
import android.widget.ProgressBar
import android.view.animation.OvershootInterpolator
import android.view.animation.DecelerateInterpolator
import androidx.annotation.RequiresPermission
import androidx.core.content.res.use
import com.google.android.material.card.MaterialCardView
import java.lang.ref.WeakReference
import java.util.*
import kotlin.concurrent.timerTask
import android.util.Log
import android.content.res.ColorStateList
import com.google.android.material.color.DynamicColors
import com.google.android.material.color.MaterialColors
import partisan.weforge.xyz.pulse.Preferences.PopupEffect
import partisan.weforge.xyz.pulse.MatrixRainView

class PopupWindow(
    ctx: Context,
    private val service: WeakReference<CallRedirectionService>?,
) {
    private val themedCtx = DynamicColors.wrapContextIfAvailable(
        ContextThemeWrapper(ctx, R.style.Theme_Pulse)
    )
    private val prefs = Preferences(themedCtx)
    private val windowManager = themedCtx.getSystemService(WindowManager::class.java)
    private val audioManager = themedCtx.getSystemService(AudioManager::class.java)
    private val inflater = LayoutInflater.from(themedCtx)
    private val view = inflater.inflate(R.layout.popup, null)
    private val layoutParams = WindowManager.LayoutParams().apply {
        format = PixelFormat.TRANSLUCENT
        flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        gravity = Gravity.BOTTOM
        width = WindowManager.LayoutParams.WRAP_CONTENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        y = prefs.popupPosition
    }
    private var currentEffect: PopupEffect = PopupEffect.NONE
    private var matrixOverlay: View? = null
    private var gamerAnimator: ValueAnimator? = null
    private var timer: Timer? = null 

    init {
        view.setOnClickListener {
            cancel()
            service?.get()?.placeCallUnmodified()
        }

        // This is utterly stupid, but it works
        applyResolvedColors(view)
    }

    fun preview(isLongPress: Boolean = false) {
        remove()
        layoutParams.y = prefs.popupPosition

        val destinations = listOf(
            R.string.destination_signal,
            R.string.destination_telegram,
            R.string.destination_threema,
            // Whatsapp smells
        )
        setDescription(destinations.random())
        add()

        val duration = if (isLongPress) prefs.redirectionDelay * 5 else prefs.redirectionDelay
        timer?.cancel()
        timer = Timer()
        timer?.schedule(timerTask {
            remove()
        }, duration)
    }

    fun show(uri: Uri, destinationId: Int) {
        val svc = service?.get() ?: return

        timer?.cancel()
        timer = Timer()
        timer?.schedule(timerTask {
            Handler(Looper.getMainLooper()).post {
                if (!remove()) {
                    svc.placeCallUnmodified()
                    return@post
                }
                if (audioManager?.mode != AudioManager.MODE_IN_CALL) {
                    svc.placeCallUnmodified()
                    return@post
                }
                try {
                    call(uri)
                } catch (exc: SecurityException) {
                    svc.placeCallUnmodified()
                    return@post
                }
                svc.cancelCall()
            }
        }, prefs.redirectionDelay)

        layoutParams.y = prefs.popupPosition
        setDescription(destinationId)
        startProgressAnimation(prefs.redirectionDelay)

        if (!add()) {
            Log.w("PopupWindow", "add() failed – popup not shown, calling directly.")
            timer?.cancel()
            svc.placeCallUnmodified()
        }
    }

    private fun setDescription(id: Int) {
        view.findViewById<TextView>(R.id.description).text = themedCtx.getString(
            R.string.popup,
            themedCtx.getString(id),
        )
    }

    private fun startProgressAnimation(duration: Long) {
        val bar = view.findViewById<ProgressBar>(R.id.progress)
        bar.max = 100
        bar.progress = 0
        val animator = ObjectAnimator.ofInt(bar, "progress", 100)
        animator.duration = duration
        animator.start()
    }

    @RequiresPermission(Manifest.permission.CALL_PHONE)
    fun call(data: Uri) {
        Intent(Intent.ACTION_VIEW).apply {
            this.data = data
            flags = Intent.FLAG_ACTIVITY_NEW_TASK
            themedCtx.startActivity(this)
        }
    }

    private fun animateAppear() {
        view.animate().cancel()
        
        // Always reset all transforms before animation
        view.rotationX = 0f
        view.alpha = 1f
        view.translationX = 0f
        view.translationY = 0f
        view.scaleX = 1f
        view.scaleY = 1f

        // Reset gamer effect if it was active before
        gamerAnimator?.cancel()
        gamerAnimator = null

        val card = view as MaterialCardView
        themedCtx.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorOutline)).use { ta ->
            val defaultStroke = ta.getColor(0, Color.DKGRAY)
            card.strokeColor = defaultStroke
        }

        val effect = when (prefs.popupEffect) {
            PopupEffect.RANDOM -> prefs.getAvailablePopupEffects().random()
            else -> prefs.popupEffect
        }
        currentEffect = effect

        when (effect) {
            PopupEffect.NONE -> {}
            PopupEffect.FADE -> {
                view.alpha = 0f
                view.animate().alpha(1f).setDuration(300).start()
            }
            PopupEffect.SCALE -> { //
                view.translationX = view.width.toFloat()
                view.alpha = 0f
                view.animate()
                    .translationX(0f)
                    .alpha(1f)
                    .setDuration(350)
                    .setInterpolator(DecelerateInterpolator(2f))
                    .start()
            }
            PopupEffect.BOUNCE -> {
                view.scaleX = 0.7f
                view.scaleY = 0.7f
                view.animate().scaleX(1f).scaleY(1f)
                    .setInterpolator(OvershootInterpolator())
                    .setDuration(400).start()
            }
            PopupEffect.FLOP -> {
                view.rotationX = 90f
                view.alpha = 0f
                view.animate().rotationX(0f).alpha(1f)
                    .setDuration(350)
                    .setInterpolator(DecelerateInterpolator())
                    .start()
            }
            PopupEffect.MATRIX -> {
                val rainView = MatrixRainView(themedCtx)
                matrixOverlay?.let {
                    try {
                        windowManager?.removeViewImmediate(it)
                    } catch (_: Exception) {}
                }
                matrixOverlay = rainView

                val popupBounds = Rect()
                view.getGlobalVisibleRect(popupBounds)

                val overlayParams = WindowManager.LayoutParams().apply {
                    type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
                    format = PixelFormat.TRANSLUCENT
                    flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    width = view.width
                    height = view.height
                    gravity = Gravity.BOTTOM
                    x = 0
                    y = layoutParams.y
                }

                try {
                    windowManager?.addView(rainView, overlayParams)
                } catch (e: Exception) {
                    Log.e("MatrixRain", "Failed to add rainView", e)
                    return
                }

                // Fade-in popup over 500ms
                view.alpha = 0f
                view.animate().cancel()
                view.animate().alpha(1f).setDuration(500).start()

                // Remove MatrixRainView in sync
                rainView.animate().alpha(0f).setDuration(500).withEndAction {
                    try {
                        windowManager?.removeView(rainView)
                        matrixOverlay = null
                    } catch (_: Exception) {}
                }.start()
            }
            PopupEffect.SLIDE_SNAP -> {
                view.translationY = 200f
                view.alpha = 0f
                view.animate()
                    .translationY(0f)
                    .alpha(1f)
                    .setDuration(350)
                    .setInterpolator(OvershootInterpolator(2f))
                    .start()
            }
            PopupEffect.GAMER_MODE -> {
                val popupCard = view as MaterialCardView
                val hsv = floatArrayOf(0f, 1f, 1f)

                gamerAnimator?.cancel() // Cancel any existing animator before starting new one

                gamerAnimator = ValueAnimator.ofFloat(0f, 360f).apply {
                    duration = 2000
                    repeatCount = ValueAnimator.INFINITE
                    addUpdateListener {
                        hsv[0] = it.animatedValue as Float
                        popupCard.strokeColor = Color.HSVToColor(hsv)
                    }
                    start()
                }

                view.alpha = 0f
                view.animate()
                    .alpha(1f)
                    .setDuration(400)
                    .start()
            }
            else -> {}
        }
    }

    private fun animateDisappear(onEnd: () -> Unit) {

        // While the reset after animation can be a nice safety net, it's causing visual glitches and is already run at the start of every animation anyway
        //     val resetAndFinish = {
        //         view.animate().cancel()
        //         view.translationX = 0f
        //         view.translationY = 0f
        //         view.scaleX = 1f
        //         view.scaleY = 1f
        //         view.rotationX = 0f
        //         view.rotationY = 0f
        //         view.alpha = 1f
        //         Log.d("PopupWindow", "Reset and finish after disappear animation")
        //         onEnd()
        //     }

        val end = Runnable {
            Log.d("PopupWindow", "Disappearance animation complete")
            view.post { onEnd() } // defer by one frame to ensure alpha=0 is rendered
        }

        when (currentEffect) {

            // PopupEffect.NONE -> falls to else
            
            PopupEffect.FADE -> view.animate()
                .alpha(0f)
                .setDuration(200)
                .withEndAction(end)
                .start()

            PopupEffect.SCALE -> view.animate()
                .translationX(view.width.toFloat())
                .alpha(0f)
                .setDuration(200)
                .setInterpolator(DecelerateInterpolator(2f))
                .withEndAction(end)
                .start()

            PopupEffect.BOUNCE -> view.animate()
                .scaleX(0f)
                .scaleY(0f)
                .setDuration(200)
                .withEndAction(end)
                .start()

            PopupEffect.FLOP -> view.animate()
                .rotationX(90f)
                .alpha(0f)
                .setDuration(200)
                .withEndAction(end)
                .start()

            PopupEffect.MATRIX -> {
                view.animate()
                    .alpha(0f)
                    .setDuration(150)
                    .withEndAction(end)
                    .start()

                matrixOverlay?.let { overlay ->
                    overlay.animate().cancel()
                    overlay.animate().alpha(0f).setDuration(150).withEndAction {
                        try {
                            windowManager?.removeViewImmediate(overlay)
                        } catch (_: Exception) {}
                        matrixOverlay = null
                    }.start()
                }
            }

            PopupEffect.SLIDE_SNAP -> view.animate()
                .translationY(200f)
                .alpha(0f)
                .setDuration(200)
                .setInterpolator(DecelerateInterpolator(2f))
                .withEndAction(end)
                .start()

            else -> end.run()
        }
    }

    private fun add(): Boolean {
        try {
            // If already attached, force remove and re-add
            if (view.parent != null) {
                windowManager?.removeViewImmediate(view)
            }
            view.animate().cancel()
            windowManager?.addView(view, layoutParams)
            animateAppear()
        } catch (exc: Exception) {
            Log.e("PopupWindow", "Failed to add popup view", exc)
            return false
        }
        return true
    }

    private fun remove(onRemoved: (() -> Unit)? = null): Boolean {
        return try {
            animateDisappear {
                try {
                    windowManager?.removeView(view)
                    matrixOverlay?.let {
                        try {
                            windowManager?.removeViewImmediate(it)
                        } catch (e: Exception) {
                            Log.e("PopupWindow", "Failed to remove matrix overlay", e)
                        }
                        matrixOverlay = null
                    }
                } catch (e: Exception) {
                    Log.e("PopupWindow", "Failed to remove popup view", e)
                }
                onRemoved?.invoke()
            }
            true
        } catch (e: Exception) {
            Log.e("PopupWindow", "Exception during remove()", e)
            false
        }
    }

    fun cancel() {
        Log.d("PopupWindow", "Cancel called")
        timer?.cancel()
        remove()
    }

    private fun applyResolvedColors(view: android.view.View) {
        val attrSurface = com.google.android.material.R.attr.colorSurface
        val attrOnSurface = com.google.android.material.R.attr.colorOnSurface
        val attrPrimary = com.google.android.material.R.attr.colorPrimaryVariant

        try {
            themedCtx.obtainStyledAttributes(intArrayOf(attrSurface, attrOnSurface, attrPrimary)).use { ta ->
                val surface = ta.getColor(0, Color.LTGRAY)
                val onSurface = ta.getColor(1, Color.DKGRAY)
                val primary = ta.getColor(2, Color.DKGRAY)

                (view as? MaterialCardView)?.setCardBackgroundColor(surface)
                view.findViewById<TextView>(R.id.description)?.setTextColor(onSurface)

                view.findViewById<ProgressBar>(R.id.progress)?.let { bar ->
                    bar.progressTintList = ColorStateList.valueOf(primary)
                    bar.progressBackgroundTintList = ColorStateList.valueOf(
                        primary and 0x00FFFFFF or (0x40 shl 24)
                    )
                }

                view.invalidate()
            }
        } catch (e: Exception) {
            Log.e("PopupTheme", "Color resolution error: ${e.message}", e)
        }
    }
}
