package com.jsjdesigns.poker

import com.jsjdesigns.poker.game.DecisionOffer
import com.jsjdesigns.poker.game.Street
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class ActionButtonsTest {

    private fun offer(
        toCall: Int = 10,
        minRaiseTo: Int = 20,
        maxRaiseTo: Int = 500,
        canCheck: Boolean = false,
        canRaise: Boolean = true,
        stack: Int = 500,
    ) = DecisionOffer(
        token = 1L,
        handNumber = 1,
        street = Street.FLOP,
        seat = 0,
        hole = listOf(0, 1),
        board = emptyList(),
        pot = 40,
        toCall = toCall,
        minRaiseTo = minRaiseTo,
        maxRaiseTo = maxRaiseTo,
        stack = stack,
        canCheck = canCheck,
        canRaise = canRaise,
        activeOpponents = 1,
        seatsActingAfter = 0,
        bigBlind = 2,
        eligiblePot = 40,
        currentBet = if (canCheck) 0 else toCall,
    )

    // ---------- the raise omission ----------

    /**
     * The UI used to require maxRaiseTo > minRaiseTo, which hid an exact-minimum
     * raise even though the engine accepts it.
     */
    @Test
    fun `an exact minimum raise is still offered`() {
        val b = buttonsFor(offer(minRaiseTo = 100, maxRaiseTo = 100))
        assertTrue("a raise with exactly one legal size must still be offered", b.showRaise)
        assertFalse("no slider is useful for a single size", b.showSlider)
        assertEquals(100, b.fixedRaiseTo)
    }

    /**
     * A stack too short for a full min-raise may still shove; the engine clamps
     * such a raise to maxRaiseTo. That was hidden too.
     */
    @Test
    fun `a short all-in is still offered`() {
        val b = buttonsFor(offer(minRaiseTo = 200, maxRaiseTo = 120, stack = 120))
        assertTrue("a legal short all-in must be offered", b.showRaise)
        assertFalse(b.showSlider)
        assertEquals(120, b.fixedRaiseTo)
        assertEquals("All in", raiseLabel(b, b.fixedRaiseTo))
    }

    @Test
    fun `a genuine range gets a slider`() {
        val b = buttonsFor(offer(minRaiseTo = 20, maxRaiseTo = 500))
        assertTrue(b.showRaise)
        assertTrue(b.showSlider)
        assertEquals(20, b.sliderMin)
        assertEquals(500, b.sliderMax)
    }

    @Test
    fun `no raise button when the engine would not accept one`() {
        assertFalse(buttonsFor(offer(canRaise = false)).showRaise)
    }

    // ---------- fold and call ----------

    @Test
    fun `fold is hidden when checking is free`() {
        assertFalse(
            "folding a free hand is never correct; offering it invites an accidental muck",
            buttonsFor(offer(toCall = 0, canCheck = true)).showFold,
        )
        assertTrue(buttonsFor(offer(toCall = 10, canCheck = false)).showFold)
    }

    @Test
    fun `the call button names the price`() {
        assertEquals("Call 35", buttonsFor(offer(toCall = 35)).checkOrCallLabel)
        assertEquals("Check", buttonsFor(offer(toCall = 0, canCheck = true)).checkOrCallLabel)
    }

    @Test
    fun `the raise label switches to all in at the top of the range`() {
        val b = buttonsFor(offer(minRaiseTo = 20, maxRaiseTo = 500))
        assertEquals("Raise 60", raiseLabel(b, 60))
        assertEquals("All in", raiseLabel(b, 500))
    }
}

class CallCostTest {

    private fun offer(toCall: Int, stack: Int) = DecisionOffer(
        token = 1L,
        handNumber = 1,
        street = Street.RIVER,
        seat = 0,
        hole = listOf(0, 1),
        board = emptyList(),
        pot = 100,
        toCall = toCall,
        minRaiseTo = 999,
        maxRaiseTo = stack,
        stack = stack,
        canCheck = false,
        canRaise = false,
        activeOpponents = 1,
        seatsActingAfter = 0,
        bigBlind = 2,
        eligiblePot = 100,
        currentBet = toCall,
    )

    /**
     * Facing more than you have left is a call for your whole stack; the engine
     * clamps the commitment. Quoting the full price promised a payment the player
     * could not make and did not owe.
     */
    @Test
    fun `facing more than the stack is labelled as an all in for the stack`() {
        val b = buttonsFor(offer(toCall = 35, stack = 20))
        assertEquals("All in 20", b.checkOrCallLabel)
        assertEquals(20, b.effectiveCall)
    }

    @Test
    fun `calling exactly the stack is also an all in`() {
        val b = buttonsFor(offer(toCall = 20, stack = 20))
        assertEquals("All in 20", b.checkOrCallLabel)
        assertEquals(20, b.effectiveCall)
    }

    @Test
    fun `an affordable call quotes the real price`() {
        val b = buttonsFor(offer(toCall = 35, stack = 500))
        assertEquals("Call 35", b.checkOrCallLabel)
        assertEquals(35, b.effectiveCall)
    }
}
