package com.ponzischeme89.memby.ui.player

import android.app.Activity
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.RadialGradient
import android.graphics.Shader
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.view.isVisible
import com.github.takahirom.roborazzi.captureRoboImage
import com.ponzischeme89.memby.R
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode

/**
 * The skip-intro offer over a stand-in for a film, captured from the real player XML at TV
 * resolution → `build/screenshots/skip-intro/`.
 *
 * The background is fake on purpose and its only job is to be *bright*: this button sits on
 * somebody's episode with no scrim and no panel behind it, so the thing worth looking at is
 * whether it still reads against a lit scene. A capture over black would prove nothing —
 * everything reads against black.
 *
 * Both focus states are captured because the button is focusable and takes focus as it
 * appears, and the pill inverts to white when it does — which the ring inside it has to
 * follow, or it draws white on white. That inversion is the case a unit test cannot see.
 * The countdown is captured at both ends of an opening: a two-minute figure, which is the
 * ordinary case for a title sequence and the one that has to fit inside the ring, and a
 * few seconds left, where the arc is nearly gone.
 */
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [34], qualifiers = "w960dp-h540dp-television-xhdpi")
class SkipIntroScreenshotTest {

    @Test
    fun `the offer as a viewer sees it`() {
        capture(
            name = "skip-intro-focused",
            label = R.string.player_skip_intro,
            focused = true,
            remainingMs = 118_000L,
        )
    }

    @Test
    fun `the offer the moment it lands`() {
        capture(
            name = "skip-intro-idle",
            label = R.string.player_skip_intro,
            focused = false,
            remainingMs = 118_000L,
        )
    }

    @Test
    fun `the last seconds of the offer`() {
        // The arc is nearly spent and the figure has dropped to a single digit. It reaches
        // zero exactly as the button goes, which is the whole reason the ring counts the
        // offer rather than the title sequence.
        capture(
            name = "skip-intro-running-out",
            label = R.string.player_skip_intro,
            focused = true,
            remainingMs = 7_000L,
        )
    }

    @Test
    fun `an automatic skip says what it did`() {
        // The same view in the same corner, wearing the timing cues' quiet plate instead of
        // the green pill, with no ring and never focused. This is the case worth looking at:
        // a notice that still reads as a button is a viewer pressing it to find out what it
        // does, and a ring on it would be a countdown to nothing.
        capture(
            name = "skip-intro-automatic-notice",
            label = R.string.player_skip_intro_skipped,
            focused = false,
            remainingMs = 0L,
            asNotice = true,
        )
    }

    private fun capture(
        name: String,
        label: Int,
        focused: Boolean,
        remainingMs: Long,
        asNotice: Boolean = false,
    ) {
        val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
        val root = FrameLayout(activity).apply { background = fakeScene() }
        val offer = LayoutInflater.from(activity)
            .inflate(R.layout.player_skip_intro, root, false)
        offer.visibility = View.VISIBLE

        offer.findViewById<TextView>(R.id.player_skip_intro_label).apply {
            setText(label)
            if (asNotice) setTextColor(Color.WHITE)
        }
        offer.findViewById<SkipIntroCountdownView>(R.id.player_skip_intro_countdown).apply {
            isVisible = !asNotice
            // A whole FROM opening, so the arc and the figure are at the size they run at.
            setRemaining(remainingMs, OFFER_LENGTH_MS)
        }
        // The same two lines PlayerActivity's dressSkipIntro applies, so the capture is of
        // the real notice rather than of a button with different words in it.
        offer.findViewById<View>(R.id.player_skip_intro_button).apply {
            if (asNotice) setBackgroundResource(R.drawable.time_remaining_cue_background)
            isFocusable = !asNotice
            isFocusableInTouchMode = !asNotice
            if (focused) requestFocus()
        }

        root.addView(offer)
        activity.setContentView(root)
        root.captureRoboImage("build/screenshots/skip-intro/$name.png")
    }

    /**
     * Stands in for a frame of an episode: a lit corner where the button sits, so the
     * capture answers "is this still legible over picture" rather than over a flat colour.
     */
    private fun fakeScene(): Drawable = object : GradientDrawable(
        Orientation.TL_BR,
        intArrayOf(Color.rgb(28, 46, 66), Color.rgb(74, 62, 44), Color.rgb(150, 132, 96)),
    ) {
        override fun draw(canvas: Canvas) {
            super.draw(canvas)
            val width = bounds.width().toFloat()
            val height = bounds.height().toFloat()
            // A bright pool of light behind the bottom-end corner — the hardest case for a
            // button with no scrim under it.
            canvas.drawPaint(
                Paint().apply {
                    shader = RadialGradient(
                        width * 0.78f, height * 0.74f, width * 0.42f,
                        intArrayOf(Color.argb(210, 255, 238, 205), Color.TRANSPARENT),
                        null,
                        Shader.TileMode.CLAMP,
                    )
                },
            )
            // And a soft horizon, so the frame reads as a scene rather than as a swatch.
            canvas.drawRect(
                0f, height * 0.62f, width, height,
                Paint().apply {
                    shader = LinearGradient(
                        0f, height * 0.62f, 0f, height,
                        Color.argb(120, 12, 16, 22), Color.argb(220, 6, 8, 12),
                        Shader.TileMode.CLAMP,
                    )
                },
            )
        }
    }

    private companion object {
        /** An episode of FROM: a two-minute opening, less the tail the offer stops short of. */
        const val OFFER_LENGTH_MS = 118_000L
    }
}
