package com.ponzischeme89.memby.ui.player

import android.app.Activity
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Shader
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
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 credits pane over a stand-in for a running credit roll, captured from the real player
 * XML at TV resolution → `build/screenshots/end-credits/`.
 *
 * The point of capturing it is the *split*, which no assertion can check: the panel is
 * measured against a picture the activity scales to half width, so the two halves have to
 * balance, and the only way to know they do is to look. The scaled picture here is drawn
 * rather than played, at exactly the transform `shrinkVideoForCredits` applies.
 *
 * There is no scrim under this panel, so the frame behind it is deliberately bright: a
 * capture over black would prove nothing about legibility — the same reason
 * `SkipIntroScreenshotTest` uses a bright scene.
 */
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [34], qualifiers = "w960dp-h540dp-television-xhdpi")
class EndCreditsScreenshotTest {

    /** The ordinary case: minutes of credits left, so no countdown yet. */
    @Test
    fun `an episode rolling its credits`() {
        capture(
            name = "end-credits",
            title = "The Crossing",
            meta = "S02E05 · Northbound",
            speed = CREDITS_TARGET_SPEED,
            countdown = null,
        )
    }

    /**
     * Inside the last minute, where the countdown appears. It lives in the pane rather than
     * in the next-up banner, which is suppressed while this is up — two of them would fight
     * over the same transform and print the episode twice.
     */
    @Test
    fun `the last minute, counting down`() {
        capture(
            name = "end-credits-countdown",
            title = "The Crossing",
            meta = "S02E05 · Northbound",
            speed = CREDITS_TARGET_SPEED,
            countdown = "Starting in 28s",
        )
    }

    /**
     * The stream could not hold 2×, so the ceiling fell. Worth a capture because the chip is
     * the only thing on screen that ever says so, and "1.5× speed" has to sit in the same
     * plate as "2× speed" without changing the layout around it.
     */
    @Test
    fun `a stream that could only manage one and a half`() {
        capture(
            name = "end-credits-reduced-speed",
            title = "The Crossing",
            meta = "S02E05 · Northbound",
            speed = 1.5f,
            countdown = null,
        )
    }

    /**
     * A long title with no episode code — the shape a series whose next entry is named but
     * unnumbered takes. The meta line is gone rather than blank, so the title has to sit
     * correctly against the artwork above it with nothing between them.
     */
    @Test
    fun `a long title and no episode metadata`() {
        capture(
            name = "end-credits-long-title",
            title = "The Cartographer of the Lower Reaches",
            meta = "",
            speed = CREDITS_TARGET_SPEED,
            countdown = null,
        )
    }

    private fun capture(
        name: String,
        title: String,
        meta: String,
        speed: Float,
        countdown: String?,
    ) {
        val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
        val root = FrameLayout(activity).apply { setBackgroundColor(Color.BLACK) }

        // The picture, where the activity's transform puts it. Scaling a view that is not
        // laid out yet does nothing, so this stands in for the PlayerView at the finished
        // scale and offset rather than trying to animate one.
        val picture = ImageView(activity).apply {
            setImageBitmap(creditRollFrame())
            scaleType = ImageView.ScaleType.FIT_CENTER
            layoutParams = FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT,
            )
            scaleX = CREDITS_VIDEO_SCALE
            scaleY = CREDITS_VIDEO_SCALE
            // The shift matters as much as the scale: without it the picture stays centred
            // and the panel is drawn over it, which is exactly the failure this capture is
            // here to catch.
            translationX = -SCREEN_WIDTH_PX * CREDITS_VIDEO_SHIFT_X
        }
        root.addView(picture)

        val pane = LayoutInflater.from(activity)
            .inflate(R.layout.player_end_credits, root, false)
        pane.visibility = View.VISIBLE
        pane.findViewById<TextView>(R.id.player_end_credits_title).text = title
        pane.findViewById<TextView>(R.id.player_end_credits_meta).apply {
            text = meta
            visibility = if (meta.isBlank()) View.GONE else View.VISIBLE
        }
        pane.findViewById<TextView>(R.id.player_end_credits_speed).text =
            activity.getString(R.string.end_credits_speed, creditsSpeedLabel(speed))
        pane.findViewById<TextView>(R.id.player_end_credits_countdown).apply {
            text = countdown.orEmpty()
            visibility = if (countdown == null) View.GONE else View.VISIBLE
        }
        pane.findViewById<ImageView>(R.id.player_end_credits_image)
            .setImageBitmap(nextEpisodeArtwork())
        // The remote lands on Play, which is what the pane does on opening — so the capture
        // shows the focus state a viewer actually sees.
        root.addView(pane)
        activity.setContentView(root)
        pane.findViewById<View>(R.id.player_end_credits_play).requestFocus()

        root.captureRoboImage("build/screenshots/end-credits/$name.png")
    }

    /**
     * Stands in for a credit roll. Deliberately bright at the top: there is no scrim between
     * the picture and the panel, and the point of the capture is that they stay separable.
     */
    private fun creditRollFrame(): Bitmap = gradient(
        width = 640,
        height = 360,
        colours = intArrayOf(
            Color.rgb(226, 222, 210),
            Color.rgb(120, 118, 112),
            Color.rgb(18, 18, 20),
        ),
    )

    private fun nextEpisodeArtwork(): Bitmap = gradient(
        width = 480,
        height = 270,
        colours = intArrayOf(
            Color.rgb(58, 92, 104),
            Color.rgb(30, 44, 58),
            Color.rgb(12, 16, 22),
        ),
    )

    private fun gradient(width: Int, height: Int, colours: IntArray): Bitmap {
        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        Canvas(bitmap).drawPaint(
            Paint().apply {
                shader = LinearGradient(
                    0f, 0f, width.toFloat(), height.toFloat(),
                    colours, null, Shader.TileMode.CLAMP,
                )
            },
        )
        return bitmap
    }

    private companion object {
        /** The width the `w960dp-…-xhdpi` qualifier gives, in pixels. */
        const val SCREEN_WIDTH_PX = 1920f
    }
}
