package com.ponzischeme89.memby.ui

import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
import androidx.test.core.app.ApplicationProvider
import com.github.takahirom.roborazzi.captureRoboImage
import com.ponzischeme89.memby.ServiceLocator
import com.ponzischeme89.memby.data.model.BaseItem
import com.ponzischeme89.memby.data.model.Studio
import com.ponzischeme89.memby.data.model.UserItemData
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone

/**
 * The finish-date estimate as it actually sits on a series hero, to
 * `build/screenshots/series-pace/`.
 *
 * ```powershell
 * .\gradlew.bat :app:testDebugUnitTest --tests "*SeriesPaceScreenshotTest"
 * ```
 *
 * The line is deliberately quiet — it is a nicety on a page whose job is Play — and quiet
 * is the one property a unit test cannot check. What is worth looking at here is whether it
 * reads as part of the progress information or as a stray sentence under the synopsis, and
 * whether the two spacing cases (with a progress bar above it and without) both hold.
 *
 * The watch history is built relative to the clock rather than pinned, because the estimate
 * is a projection from *now* and a fixture dated 2026 would fall out of the recency window
 * and capture the empty case by accident. That makes the date in the image move with the
 * day it was rendered on; these are artifacts to look at, not checked-in goldens.
 */
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [34], qualifiers = "w960dp-h540dp-television-xhdpi")
class SeriesPaceScreenshotTest {

    @get:Rule
    val compose = createComposeRule()

    @Before
    fun locator() {
        ServiceLocator.init(ApplicationProvider.getApplicationContext())
    }

    /**
     * An episode a night for the past four nights, five left, and nothing part-watched —
     * so there is no progress bar and the estimate is a line of its own. This is the state
     * the feature exists for: somebody who finished one last night and has not started the
     * next, whom the page previously told nothing at all.
     */
    @Test
    fun `an episode a night`() {
        capture("sp_series-finish") {
            SeriesDetailContent(
                item = endedSeries,
                episodes = nightly(watched = 4, remaining = 5),
                loadFailed = false,
                onPlay = {},
                onToggleFavorite = { _, _ -> },
                isMyShow = false,
                onToggleMyShow = { _, _ -> },
            )
        }
    }

    /** The other spacing case: part-way through an episode, so the bar is above the line. */
    @Test
    fun `part way through tonight's episode`() {
        val episodes = nightly(watched = 4, remaining = 5).map { item ->
            if (item.indexNumber == 5) {
                item.copy(userData = UserItemData(playbackPositionTicks = 14L * 600_000_000L))
            } else {
                item
            }
        }
        capture("sp_series-finish-resuming") {
            SeriesDetailContent(
                item = endedSeries,
                episodes = episodes,
                loadFailed = false,
                onPlay = {},
                onToggleFavorite = { _, _ -> },
                isMyShow = false,
                onToggleMyShow = { _, _ -> },
            )
        }
    }

    /** A show still in production is caught up with, never finished. */
    @Test
    fun `a show still being made`() {
        capture("sp_series-catch-up") {
            SeriesDetailContent(
                item = endedSeries.copy(status = "Continuing"),
                episodes = nightly(watched = 4, remaining = 5),
                loadFailed = false,
                onPlay = {},
                onToggleFavorite = { _, _ -> },
                isMyShow = true,
                onToggleMyShow = { _, _ -> },
            )
        }
    }

    /** Three a night across two nights, six left: the near end of the wording. */
    @Test
    fun `a binge finishes tomorrow`() {
        val watched = listOf(
            stamp(daysAgo = 2, hour = 19), stamp(daysAgo = 2, hour = 20), stamp(daysAgo = 2, hour = 21),
            stamp(daysAgo = 1, hour = 19), stamp(daysAgo = 1, hour = 20), stamp(daysAgo = 1, hour = 21),
        )
        capture("sp_series-tomorrow") {
            SeriesDetailContent(
                item = endedSeries,
                episodes = library(watched, remaining = 6),
                loadFailed = false,
                onPlay = {},
                onToggleFavorite = { _, _ -> },
                isMyShow = false,
                onToggleMyShow = { _, _ -> },
            )
        }
    }

    /**
     * The control, and the case that matters most: one episode watched is not a pace, so
     * the page says nothing. Compare against `sp_series-finish` — the hero must be
     * identical apart from the missing line, with nothing left holding its space.
     */
    @Test
    fun `not enough history to say`() {
        capture("sp_series-none") {
            SeriesDetailContent(
                item = endedSeries,
                episodes = nightly(watched = 1, remaining = 8),
                loadFailed = false,
                onPlay = {},
                onToggleFavorite = { _, _ -> },
                isMyShow = false,
                onToggleMyShow = { _, _ -> },
            )
        }
    }

    private fun capture(name: String, content: @Composable () -> Unit) {
        compose.setContent {
            PreviewSurface(alignment = Alignment.TopStart) { content() }
        }
        compose.onRoot().captureRoboImage("build/screenshots/series-pace/$name.png")
    }

    // ---------------------------------------------------------------------------

    /** [watched] episodes on consecutive nights ending last night, then [remaining] unwatched. */
    private fun nightly(watched: Int, remaining: Int): List<BaseItem> =
        library((0 until watched).map { stamp(daysAgo = watched - it, hour = 20) }, remaining)

    private fun library(watched: List<String>, remaining: Int): List<BaseItem> =
        watched.mapIndexed { index, at -> episode(index + 1, at) } +
            (0 until remaining).map { episode(watched.size + it + 1, null) }

    private fun episode(number: Int, playedAt: String?) = BaseItem(
        id = "s1e$number",
        name = EPISODE_TITLES[(number - 1) % EPISODE_TITLES.size],
        type = "Episode",
        seriesName = "Signal Hill",
        parentIndexNumber = 1,
        indexNumber = number,
        runTimeTicks = 48L * 600_000_000L,
        overview = "The night shift picks up a voice reading tomorrow's shipping forecast, " +
            "and the log book from 1974 says the same thing happened before.",
        userData = UserItemData(played = playedAt != null, lastPlayedDate = playedAt),
    )

    /** Emby's UTC stamp for [hour] o'clock, [daysAgo] days back. */
    private fun stamp(daysAgo: Int, hour: Int): String {
        val at = System.currentTimeMillis() - daysAgo * 86_400_000L
        val midnight = at / 86_400_000L * 86_400_000L
        return iso.format(java.util.Date(midnight + hour * 3_600_000L))
    }

    private val iso = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
        timeZone = TimeZone.getTimeZone("UTC")
    }

    private val endedSeries = BaseItem(
        id = "series-1",
        name = "Signal Hill",
        type = "Series",
        overview = "A coastal radio station keeps receiving a broadcast that has not been " +
            "transmitted yet. Six weeks before the storm, the night operator starts writing " +
            "down what she hears.",
        productionYear = 2022,
        officialRating = "TV-MA",
        genres = listOf("Thriller", "Drama"),
        studios = listOf(Studio(name = "Harbour Line")),
        status = "Ended",
    )

    private companion object {
        val EPISODE_TITLES = listOf(
            "Carrier Wave", "Dead Air", "The Long Count", "Nightingale", "Six Weeks Out",
            "Landfall", "The Shipping Forecast", "Quiet Hours", "Storm Glass", "Last Transmission",
            "Harbour Line", "The Night Operator", "Signal Hill",
        )
    }
}
