package com.jsjdesigns.poker.core

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class HandEvaluatorTest {

    private fun eval(text: String) = HandEvaluator.evaluate(cardsOf(text))
    private fun cat(text: String) = HandEvaluator.categoryOf(eval(text))

    @Test
    fun `categories are detected`() {
        assertEquals(HandEvaluator.STRAIGHT_FLUSH, cat("9h 8h 7h 6h 5h"))
        assertEquals(HandEvaluator.QUADS, cat("9h 9s 9d 9c 5h"))
        assertEquals(HandEvaluator.FULL_HOUSE, cat("9h 9s 9d 5c 5h"))
        assertEquals(HandEvaluator.FLUSH, cat("Ah Jh 7h 4h 2h"))
        assertEquals(HandEvaluator.STRAIGHT, cat("9h 8s 7d 6c 5h"))
        assertEquals(HandEvaluator.TRIPS, cat("9h 9s 9d 6c 5h"))
        assertEquals(HandEvaluator.TWO_PAIR, cat("9h 9s 6d 6c 5h"))
        assertEquals(HandEvaluator.PAIR, cat("9h 9s 8d 6c 5h"))
        assertEquals(HandEvaluator.HIGH_CARD, cat("Ah Js 8d 6c 5h"))
    }

    @Test
    fun `wheel counts as a five high straight`() {
        assertEquals(HandEvaluator.STRAIGHT, cat("Ah 2s 3d 4c 5h"))
        // ...and must lose to a six-high straight
        assertTrue(eval("Ah 2s 3d 4c 5h") < eval("2s 3d 4c 5h 6d"))
    }

    @Test
    fun `steel wheel is a straight flush`() {
        assertEquals(HandEvaluator.STRAIGHT_FLUSH, cat("Ah 2h 3h 4h 5h"))
        assertTrue(eval("Ah 2h 3h 4h 5h") < eval("6h 2h 3h 4h 5h"))
    }

    @Test
    fun `ace high straight beats king high straight`() {
        assertTrue(eval("Ah Ks Qd Jc Th") > eval("Ks Qd Jc Th 9h"))
    }

    @Test
    fun `category ordering holds`() {
        val ascending = listOf(
            "Ah Js 8d 6c 5h",   // high card
            "9h 9s 8d 6c 5h",   // pair
            "9h 9s 6d 6c 5h",   // two pair
            "9h 9s 9d 6c 5h",   // trips
            "9h 8s 7d 6c 5h",   // straight
            "Ah Jh 7h 4h 2h",   // flush
            "9h 9s 9d 5c 5h",   // full house
            "9h 9s 9d 9c 5h",   // quads
            "9h 8h 7h 6h 5h",   // straight flush
        ).map { eval(it) }

        for (i in 1 until ascending.size) {
            assertTrue(ascending[i] > ascending[i - 1], "rank $i should beat rank ${i - 1}")
        }
    }

    @Test
    fun `kickers break ties`() {
        assertTrue(eval("9h 9s Ad 6c 5h") > eval("9h 9s Kd 6c 5h"))
        assertTrue(eval("9h 9s 6d 6c Ah") > eval("9h 9s 6d 6c Kh"))
        assertTrue(eval("Ah As Ad Ac Kh") > eval("Ah As Ad Ac Qh"))
        assertEquals(eval("9h 9s 6d 6c Ah"), eval("9d 9c 6s 6h As"))
    }

    @Test
    fun `two trips make a full house using the higher set`() {
        // 7s full of 5s, not 5s full of 7s
        val score = eval("7h 7s 7d 5c 5h 5s 2d")
        assertEquals(HandEvaluator.FULL_HOUSE, HandEvaluator.categoryOf(score))
        assertEquals(score, eval("7h 7s 7d 5c 5h"))
    }

    @Test
    fun `seven cards pick the best five`() {
        // Flush is available and must be chosen over the pair
        assertEquals(HandEvaluator.FLUSH, HandEvaluator.categoryOf(eval("Ah Jh 7h 4h 2h 9s 9d")))
        // Straight flush available among seven
        assertEquals(HandEvaluator.STRAIGHT_FLUSH, HandEvaluator.categoryOf(eval("9h 8h 7h 6h 5h As Kd")))
        // Six cards to a flush -> best five of that suit
        assertEquals(eval("Ah Kh Qh Jh 9h"), eval("Ah Kh Qh Jh 9h 2h 3s"))
    }

    @Test
    fun `board plays when hole cards do not improve it`() {
        val board = "Ah Kh Qh Jh Th"
        assertEquals(eval("$board 2c 3d"), eval("$board 2s 3h"))
    }

    /**
     * Exhaustive check against the known frequencies of five-card poker hands.
     * If any branch of the evaluator is wrong these counts move, so this is the
     * test that actually proves correctness rather than spot-checking it.
     */
    @Test
    fun `all 2598960 five card hands match published frequencies`() {
        val expected = intArrayOf(
            1302540, // high card
            1098240, // pair
            123552,  // two pair
            54912,   // trips
            10200,   // straight
            5108,    // flush
            3744,    // full house
            624,     // quads
            40,      // straight flush
        )

        val counts = IntArray(9)
        val hand = IntArray(5)
        var total = 0

        for (a in 0 until 48) {
            hand[0] = a
            for (b in a + 1 until 49) {
                hand[1] = b
                for (c in b + 1 until 50) {
                    hand[2] = c
                    for (d in c + 1 until 51) {
                        hand[3] = d
                        for (e in d + 1 until 52) {
                            hand[4] = e
                            counts[HandEvaluator.categoryOf(HandEvaluator.evaluate(hand))]++
                            total++
                        }
                    }
                }
            }
        }

        assertEquals(2_598_960, total)
        for (i in 0..8) {
            assertEquals(
                expected[i], counts[i],
                "${HandEvaluator.CATEGORY_NAMES[i]} count mismatch",
            )
        }
    }
}
