package ch.dissem.yaep.ui.common

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SkikoComposeUiTest
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performKeyInput
import androidx.compose.ui.test.pressKey
import androidx.compose.ui.test.runSkikoComposeUiTest
import androidx.compose.ui.test.withKeyDown
import androidx.compose.ui.unit.dp
import ch.dissem.yaep.domain.Game
import ch.dissem.yaep.domain.UnsolvablePuzzleException
import ch.dissem.yaep.ui.common.focus.FocusFollowingSelectionManager
import ch.dissem.yaep.ui.common.focus.GridSelectionManager
import ch.tutteli.atrium.api.fluent.en_GB.notToEqualNull
import ch.tutteli.atrium.api.fluent.en_GB.toBeAnInstanceOf
import ch.tutteli.atrium.api.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.fluent.en_GB.toHaveElementsAndAll
import ch.tutteli.atrium.api.fluent.en_GB.toHaveSize
import ch.tutteli.atrium.api.verbs.expect
import kotlin.test.Test
import kotlin.test.fail

@OptIn(ExperimentalTestApi::class)
class AppTest {
    val game = createGame()
    var resetCluesBeacon by mutableStateOf(Any())

    @Test
    fun ensure_app_can_be_rendered_on_1920x1080_and_can_be_solved() = showApp(
        Size(1920f, 1080f)
    ) {
        val clues = game.clues
        val grid = game.grid
        var removedOptions: Boolean
        do {
            removedOptions = false
            try {
                clues.forEach { clue ->
                    val optionsRemoved = clue.removeForbiddenOptions(grid)
                    removedOptions = optionsRemoved || removedOptions
                    if (optionsRemoved) {
                        resetCluesBeacon = Any()
                    }
                }
            } catch (_: UnsolvablePuzzleException) {
                fail("puzzle can't be solved")
            }
            grid.cleanupOptions()
            resetCluesBeacon = Any()
        } while (removedOptions)

        expect(game.isSolved).toEqual(true)
        onNodeWithTag("EndOfGame.solved_time")
            .assertExists("End of Game must be shown when puzzle is solved")
    }

    @Test
    fun ensure_app_can_be_rendered_on_1080x1920_and_keyboard_selection_and_deselection_works() = showApp(
        Size(1080f, 1920f)
    ) {
        expect(game.grid[0][1].selection).toEqual(null)
        expect(FocusFollowingSelectionManager.focused).notToEqualNull()
        expect(FocusFollowingSelectionManager.focused?.child).toBeAnInstanceOf<GridSelectionManager>()
        onRoot().performKeyInput {
            pressKey(Key.DirectionRight)
            pressKey(Key.One)
        }
        expect(game.grid[0][1].selection).notToEqualNull()

        onRoot().performKeyInput {
            pressKey(Key.Backspace)
        }
        expect(game.grid[0][1].selection).toEqual(null)
        expect(game.grid.map { it.options }).toHaveElementsAndAll {
            toHaveSize(6)
        }
    }

    @Test
    fun ensure_app_can_be_rendered_on_800x600_and_keyboard_option_removal_works() = showApp(
        Size(800f, 600f)
    ) {
        val gridCell = game.grid[0][1]

        expect(gridCell.selection).toEqual(null)
        onRoot().performKeyInput {
            pressKey(Key.DirectionRight)
            withKeyDown(Key.ShiftLeft) {
                expect(gridCell.options).toHaveSize(6)
                pressKey(Key.One)
                expect(gridCell.options).toHaveSize(5)
                pressKey(Key.Two)
                expect(gridCell.options).toHaveSize(4)
                pressKey(Key.Three)
                expect(gridCell.options).toHaveSize(3)
                pressKey(Key.Four)
                expect(gridCell.options).toHaveSize(2)
                pressKey(Key.Five)
                expect(gridCell.options).toHaveSize(1)
            }
        }
        expect(gridCell.selection).notToEqualNull()
    }

    @Test
    fun ensure_app_can_be_rendered_on_600x800_and_numpad_option_selection_works() = showApp(
        Size(600f, 800f)
    ) {
        val gridCell = game.grid[0][1]

        onRoot().performKeyInput {
            pressKey(Key.DirectionRight)
            withKeyDown(Key.ShiftLeft) {
                expect(gridCell.options).toHaveSize(6)
                pressKey(Key.NumPad1)
                expect(gridCell.options).toHaveSize(5)
                pressKey(Key.NumPad5)
                expect(gridCell.options).toHaveSize(4)
                pressKey(Key.NumPad5)
                expect(gridCell.options).toHaveSize(5)
                pressKey(Key.NumPad4)
                expect(gridCell.options).toHaveSize(4)
            }
        }
    }

    fun showApp(
        screenSize: Size,
        block: suspend SkikoComposeUiTest.() -> Unit = {}
    ) = runSkikoComposeUiTest(size = screenSize) {
        setContent {
            App(
                modifier = Modifier.onKeyEvent { event ->
                    FocusFollowingSelectionManager.onKeyEvent(event)
                },
                rootSelectionManager = FocusFollowingSelectionManager,
                spacing = 8.dp,
                game = game,
                onNewGame = { },
                resetCluesBeacon = resetCluesBeacon
            )
        }
        block()
    }

    private fun createGame(): Game = Game.parse(
        """
        * MATE is between the neighbours TEACHER and GOAT to both sides
        * SCIENTIST is between the neighbours SPAIN and FIREFIGHTER to both sides
        * TEA is left of COFFEE
        * ZEBRA is between the neighbours LEMON and CAKE to both sides
        * CUPCAKE is left of GOAT
        * ROCK_STAR is left of SPAIN
        * PEAR is at position 1
        * CHOCOLATE is between the neighbours TEA and MILK to both sides
        * FARMER is between the neighbours PEAR and CHERRIES to both sides
        * SWITZERLAND is between the neighbours JAPAN and DOG to both sides
        * MANGO and SPAIN are in the same column
        * SPAIN is next to SNAIL
        * CANADA is next to PIE
        * JAPAN is between the neighbours SWITZERLAND and CAKE to both sides
        * FIREFIGHTER is between the neighbours GOAT and STRAWBERRY to both sides
        * UKRAINE is between the neighbours ANT and BEVERAGE to both sides
        * CHOCOLATE and SWITZERLAND are in the same column
        * PEAR is left of COFFEE
        * JAPAN is between the neighbours CAKE and COFFEE to both sides
        * BEVERAGE is between the neighbours CUSTARD and UKRAINE to both sides
        * CANADA and CAKE are in the same column
        """.trimIndent()
    )

}