package com.jsjdesigns.poker

import com.jsjdesigns.poker.game.HandResult
import com.jsjdesigns.poker.game.Street
import com.jsjdesigns.poker.game.TableSnapshot
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test

class HandSummaryTest {

    private fun result(
        net: IntArray = intArrayOf(-8, 8, 0),
        winners: List<Int> = listOf(1),
        showdown: Boolean = true,
        pot: Int = 16,
    ) = HandResult(
        board = intArrayOf(0, 1, 2, 3, 4),
        net = net,
        winners = winners,
        wentToShowdown = showdown,
        potSize = pot,
        events = emptyList(),
    )

    @Test
    fun `summary names the winner and reports the hero's actual net`() {
        val summary = handSummaryFor(
            handNumber = 7,
            result = result(),
            seatNames = listOf("You", "Ada", "Bruno"),
        )

        assertEquals("Ada wins", summary.title)
        assertEquals("Showdown  •  Pot 16  •  You lost 8", summary.detail)
        assertEquals(7, summary.handNumber)
    }

    @Test
    fun `hero is identified by seat when the player uses a custom name`() {
        val summary = handSummaryFor(
            handNumber = 3,
            result = result(
                net = intArrayOf(5, -2, -3),
                winners = listOf(0),
                showdown = false,
                pot = 6,
            ),
            seatNames = listOf("Jay", "Ada", "Bruno"),
        )

        assertEquals("You win", summary.title)
        assertTrue(summary.heroWon)
        assertEquals("Uncontested  •  Pot 6  •  You won 5", summary.detail)
    }

    @Test
    fun `custom-named hero is labelled as you among several winners`() {
        val summary = handSummaryFor(
            handNumber = 9,
            result = result(
                net = intArrayOf(0, 12, -12),
                winners = listOf(0, 1),
                pot = 40,
            ),
            seatNames = listOf("Jay", "Ada", "Bruno"),
        )

        assertEquals("You & Ada win", summary.title)
        assertTrue(summary.heroWon)
        assertTrue(summary.detail.endsWith("You broke even"))
    }
}

class VisibleHandSummaryTest {

    private val summary = HandSummary(
        handNumber = 4,
        winnerLabels = listOf("Ada"),
        heroWon = false,
        potSize = 20,
        heroNet = -4,
        wentToShowdown = true,
    )

    private fun snapshot(
        handNumber: Int = 4,
        phase: TableSnapshot.Phase = TableSnapshot.Phase.SHOWDOWN,
    ) = TableSnapshot(
        handNumber = handNumber,
        street = Street.RIVER,
        phase = phase,
        board = emptyList(),
        pot = 20,
        currentBet = 0,
        minRaiseSize = 2,
        button = 0,
        seats = emptyList(),
        toAct = null,
        toActToken = null,
        lastAction = null,
    )

    @Test
    fun `summary is visible only on its own terminal frame`() {
        assertEquals(
            summary,
            UiState(snapshot = snapshot(), handSummary = summary).visibleHandSummary(),
        )
        assertNull(
            UiState(snapshot = snapshot(handNumber = 5), handSummary = summary).visibleHandSummary(),
        )
        assertNull(
            UiState(
                snapshot = snapshot(phase = TableSnapshot.Phase.BETTING),
                handSummary = summary,
            ).visibleHandSummary(),
        )
    }
}

@OptIn(ExperimentalCoroutinesApi::class)
class NextHandGateTest {

    @Test
    fun `a duplicate old tap cannot release a future hand`() = runTest {
        val gate = NextHandGate()
        gate.request(4)
        gate.awaitRequest(4)

        val next = async { gate.awaitRequest(5) }
        runCurrent()
        assertFalse(next.isCompleted)

        gate.request(4)
        runCurrent()
        assertFalse(next.isCompleted)

        gate.request(5)
        runCurrent()
        assertTrue(next.isCompleted)
    }
}
