package com.ponzischeme89.memby.ui.settings

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import com.github.takahirom.roborazzi.captureRoboImage
import com.ponzischeme89.memby.data.ImageCacheSize
import com.ponzischeme89.memby.ui.PreviewSurface
import kotlinx.coroutines.delay
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
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

/**
 * Captures the real stateless Settings panel at TV size. The fixture deliberately mixes
 * enabled and disabled values so the screenshot proves state is legible without focus;
 * the first row also owns focus to prove the white focus ring and green active state are
 * visually distinct.
 */
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [34], qualifiers = "w960dp-h540dp-television-xhdpi")
class SettingsSheetScreenshotTest {

    @get:Rule
    val compose = createComposeRule()

    @Test
    fun `active inactive and focused controls`() {
        compose.setContent { SettingsPreviewFixture(overlay = false) }
        compose.onRoot().captureRoboImage("build/screenshots/settings/settings-panel.png")
    }

    @Test
    fun `home overlay width`() {
        compose.setContent { SettingsPreviewFixture(overlay = true) }
        compose.onRoot().captureRoboImage("build/screenshots/settings/settings-overlay.png")
    }

    @Test
    fun `playback reminder option`() {
        compose.setContent {
            SettingsPreviewFixture(overlay = false, selectedPage = SettingsPage.PLAYBACK)
        }
        compose.onRoot().captureRoboImage("build/screenshots/settings/settings-playback.png")
    }

    @Test
    fun `watched movie visibility option`() {
        compose.setContent {
            SettingsPreviewFixture(overlay = false, selectedPage = SettingsPage.HOME)
        }
        compose.onRoot().captureRoboImage("build/screenshots/settings/settings-home.png")
    }

    /**
     * The welcome tone lives on Appearance now, under the colour and logo rows. The capture
     * is of the whole page rather than the chip row, because the thing worth looking at is
     * whether a fourth question fits on it.
     */
    @Test
    fun `welcome tone options`() {
        compose.setContent {
            SettingsPreviewFixture(overlay = false, selectedPage = SettingsPage.APPEARANCE)
        }
        compose.onRoot().captureRoboImage("build/screenshots/settings/settings-appearance.png")
    }

    @Test
    fun `signed-in devices`() {
        compose.setContent {
            SettingsPreviewFixture(overlay = false, selectedPage = SettingsPage.DEVICES)
        }
        compose.onRoot().captureRoboImage("build/screenshots/settings/settings-devices.png")
    }

    @Test
    fun `stored artwork page`() {
        compose.setContent {
            SettingsPreviewFixture(overlay = false, selectedPage = SettingsPage.STORAGE)
        }
        compose.onRoot().captureRoboImage("build/screenshots/settings/settings-storage.png")
    }

    @Test
    fun `every rail destination maps to its page`() {
        compose.setContent { InteractiveSettingsFixture() }
        compose.waitForIdle()

        SettingsPage.entries.drop(1).forEach { expected ->
            compose.onNodeWithTag("settings-rail-${expected.name.lowercase()}").performClick()
            compose.waitForIdle()
            compose.onNodeWithTag("settings-page-${expected.name.lowercase()}").assertExists()
        }
    }

    // The other half of the rail — selection by *focus*, through SETTINGS_PAGE_SETTLE_MS —
    // is deliberately not tested here. Robolectric's host view never takes window focus, so
    // a semantics requestFocus on a rail item does not reach onFocusChanged and the settle
    // never starts; a test written against it would pass or fail for reasons that have
    // nothing to do with the rail. It is exercised on a television.

    /**
     * Every switch on every page turns on. The pages are walked by the rail, because a
     * control that cannot be reached is as good as one that does not work.
     */
    @Test
    fun `every toggle on every page can be turned on`() {
        val turnedOn = mutableSetOf<String>()
        compose.setContent { InteractiveSettingsFixture(onToggled = { turnedOn += it }) }
        compose.waitForIdle()

        val togglesByPage = mapOf(
            SettingsPage.APPEARANCE to listOf("Show logos"),
            SettingsPage.PLAYBACK to listOf(
                "Ten minutes left",
                "Play the next episode",
                "Closing credits",
            ),
            SettingsPage.HOME to listOf(
                "Continue watching",
                "Favourites",
                "Latest movies",
                "Hide films you have seen",
                "Text under cards",
                "Ratings",
            ),
        )
        togglesByPage.forEach { (page, titles) ->
            compose.onNodeWithTag("settings-rail-${page.name.lowercase()}").performClick()
            compose.waitForIdle()
            titles.forEach { title ->
                compose.onNodeWithText(title).performScrollTo().performClick()
                compose.waitForIdle()
            }
        }
        assertEquals(togglesByPage.values.flatten().toSet(), turnedOn)
    }

    @Test
    fun `about lists the release history and opens one release`() {
        compose.setContent { InteractiveSettingsFixture() }
        compose.waitForIdle()
        compose.onNodeWithTag("settings-rail-about").performClick()
        compose.waitForIdle()

        val releases = MembyReleaseHistory
        assertTrue("the shipped changelog parsed to nothing", releases.size >= 2)
        compose.onNodeWithTag("settings-release-${releases[1].version}")
            .performScrollTo()
            .performClick()
        compose.waitForIdle()
        compose.onNodeWithText(releases[1].changes.first()).assertExists()
    }

    @Test
    fun `about version history`() {
        compose.setContent {
            SettingsPreviewFixture(overlay = false, selectedPage = SettingsPage.ABOUT)
        }
        compose.onRoot().captureRoboImage("build/screenshots/settings/settings-about.png")
    }

    /**
     * The panel wired to real state, so a press changes something a test can read. Every
     * switch starts off: the assertion worth making is that each one can be turned *on*,
     * which a fixture holding the defaults could not tell apart from a press doing nothing.
     */
    @Composable
    private fun InteractiveSettingsFixture(onToggled: (String) -> Unit = {}) {
        var selectedPage by remember { mutableStateOf(SettingsPage.APPEARANCE) }
        var showLogo by remember { mutableStateOf(false) }
        var autoPlayNext by remember { mutableStateOf(false) }
        var tenMinutes by remember { mutableStateOf(false) }
        var speedUpCredits by remember { mutableStateOf(false) }
        var hideWatched by remember { mutableStateOf(false) }
        var cardMetadata by remember { mutableStateOf(false) }
        var ratings by remember { mutableStateOf(false) }
        var homeSections by remember { mutableStateOf(emptySet<String>()) }
        val firstFocus = remember { FocusRequester() }
        LaunchedEffect(Unit) {
            delay(170)
            firstFocus.requestFocus()
        }
        // Only an on counts. A recorded off would let a row that reported the wrong
        // direction pass the test that exists to catch exactly that.
        fun record(title: String, enabled: Boolean) {
            if (enabled) onToggled(title)
        }
        PreviewSurface {
            SettingsPanelContent(
                state = SettingsPanelState(
                    showLogo = showLogo,
                    autoPlayNext = autoPlayNext,
                    showTenMinuteReminder = tenMinutes,
                    speedUpCredits = speedUpCredits,
                    hideWatchedMovies = hideWatched,
                    showCardMetadata = cardMetadata,
                    showRatingsStrip = ratings,
                    homeSections = homeSections,
                    selectedPage = selectedPage,
                    installedVersion = "0.1.60",
                ),
                actions = SettingsPanelActions(
                    onPageSelected = { selectedPage = it },
                    onShowLogoChanged = { showLogo = it; record("Show logos", it) },
                    onAutoPlayNextChanged = {
                        autoPlayNext = it
                        record("Play the next episode", it)
                    },
                    onShowTenMinuteReminderChanged = {
                        tenMinutes = it
                        record("Ten minutes left", it)
                    },
                    onSpeedUpCreditsChanged = {
                        speedUpCredits = it
                        record("Closing credits", it)
                    },
                    onHideWatchedMoviesChanged = {
                        hideWatched = it
                        record("Hide films you have seen", it)
                    },
                    onShowCardMetadataChanged = {
                        cardMetadata = it
                        record("Text under cards", it)
                    },
                    onShowRatingsStripChanged = { ratings = it; record("Ratings", it) },
                    onHomeSectionChanged = { key, enabled ->
                        homeSections = if (enabled) homeSections + key else homeSections - key
                        record(
                            when (key) {
                                "continue" -> "Continue watching"
                                "favorites" -> "Favourites"
                                else -> "Latest movies"
                            },
                            enabled,
                        )
                    },
                ),
                overlay = false,
                firstFocusRequester = firstFocus,
            )
        }
    }

    @Composable
    private fun SettingsPreviewFixture(
        overlay: Boolean,
        selectedPage: SettingsPage = if (overlay) SettingsPage.DEVICES else SettingsPage.APPEARANCE,
    ) {
        val firstFocus = remember { FocusRequester() }
        LaunchedEffect(Unit) { firstFocus.requestFocus() }
        PreviewSurface(alignment = if (overlay) Alignment.CenterEnd else Alignment.Center) {
            SettingsPanelContent(
                state = SettingsPanelState(
                    showLogo = true,
                    autoPlayNext = false,
                    showTenMinuteReminder = false,
                    ringColor = "52B54B",
                    homeSections = setOf("continue", "latest"),
                    cardDensity = "standard",
                    showCardMetadata = false,
                    welcomeQuoteStyle = "homicidal",
                    selectedPage = selectedPage,
                    installedVersion = "0.1.60",
                    // A measured cache rather than the null the page opens on: the capture
                    // worth looking at is the one with figures in it.
                    imageCacheSize = ImageCacheSize(
                        diskBytes = 96L * 1024L * 1024L,
                        memoryBytes = 21L * 1024L * 1024L,
                    ),
                ),
                actions = SettingsPanelActions(),
                overlay = overlay,
                firstFocusRequester = firstFocus,
            )
        }
    }
}
