package com.jsjdesigns.poker.core

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

class PreflopChartTest {

    private fun p(text: String) = PreflopChart.percentile(cardsOf(text))

    /** Lower percentile == stronger hand. */
    private fun assertStronger(better: String, worse: String) {
        assertTrue(
            p(better) < p(worse),
            "$better (${"%.3f".format(p(better))}) should rank above $worse (${"%.3f".format(p(worse))})",
        )
    }

    @Test
    fun `aces are the best starting hand`() {
        assertEquals(0.0, p("Ah Ad"))
    }

    @Test
    fun `big pairs are correctly ordered`() {
        assertStronger("Ah Ad", "Kh Kd")
        assertStronger("Kh Kd", "Qh Qd")
        assertStronger("Qh Qd", "Jh Jd")
    }

    @Test
    fun `suitedness is worth something`() {
        assertStronger("Ah Kh", "Ah Kd")
        assertStronger("7h 6h", "7h 6d")
        assertStronger("Jh Th", "Jh Td")
    }

    @Test
    fun `connectedness beats equivalent gappers`() {
        assertStronger("7h 6h", "7h 2h")
        assertStronger("9h 8h", "9h 4h")
    }

    @Test
    fun `weak aces are discounted for domination`() {
        // A2o has strong all-in equity but is a reverse-implied-odds trap.
        assertStronger("Ah Kd", "Ah 2d")
        assertStronger("Ah Qd", "Ah 3d")
    }

    @Test
    fun `suited connectors outrank junk with similar raw equity`() {
        assertStronger("7h 6h", "7h 2d")
        assertStronger("6h 5h", "Kd 3c")
    }

    @Test
    fun `worst hands really are worst`() {
        assertTrue(p("7h 2d") > 0.90, "72o should sit in the bottom 10%, was ${p("7h 2d")}")
        assertTrue(p("8h 3d") > 0.85, "83o should be near the bottom, was ${p("8h 3d")}")
    }

    @Test
    fun `card order does not matter`() {
        assertEquals(p("Ah Kd"), p("Kd Ah"))
        assertEquals(p("7h 6h"), p("6h 7h"))
    }

    @Test
    fun `all percentiles are within range and reasonably distributed`() {
        val samples = listOf("Ah Ad", "Kh Qh", "9h 8d", "7h 2d", "Th Td", "Ah 5h")
        for (s in samples) {
            val v = p(s)
            assertTrue(v in 0.0..1.0, "$s percentile out of range: $v")
        }
        // A premium hand and a trash hand must not land close together.
        assertTrue(p("7h 2d") - p("Ah Ad") > 0.8, "range is too compressed to gate on")
    }

    /**
     * The percentile must be weighted by how often each class is dealt, not by
     * class count. The 169 classes are not equally likely — a pair is 6 of the
     * 1326 combinations, a suited hand 4, an offsuit hand 12 — so an unweighted
     * ranking would make "top 12%" mean 12% of classes, which is not what a
     * player means and would make `looseness` lie.
     */
    @Test
    fun `a range gate admits that share of actually dealt hands`() {
        val all = ArrayList<Double>()
        for (a in 0 until 51) for (b in a + 1 until 52) {
            all.add(PreflopChart.percentile(intArrayOf(a, b)))
        }
        assertEquals(1326, all.size, "there are C(52,2) starting combinations")

        for (gate in listOf(0.12, 0.25, 0.40, 0.60)) {
            val admitted = all.count { it <= gate }
            val share = admitted.toDouble() / all.size
            // Tolerance covers landing mid-class: one class can straddle the gate.
            assertTrue(
                kotlin.math.abs(share - gate) < 0.03,
                "gate $gate should admit ~${(gate * 100).toInt()}% of dealt hands, admitted ${"%.1f".format(share * 100)}%",
            )
        }
    }

    @Test
    fun `pairs are weighted as six combinations not one class`() {
        // Between AA (0.0) and KK there should be exactly 6/1326 of the range,
        // because AA occupies six combinations.
        val gap = p("Kh Kd") - p("Ah Ad")
        assertEquals(6.0 / 1326.0, gap, 1e-9, "AA must consume 6 combinations of the range")
    }
}
