package com.jsjdesigns.poker.sim

import com.jsjdesigns.poker.bot.SkillLevel
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class CalibrationPolicyTest {

    private fun seed(
        id: Long,
        expert: Double,
        advanced: Double,
        intermediate: Double,
        beginner: Double,
    ) = LadderSeedResult(
        seed = id,
        bbPer100 = mapOf(
            SkillLevel.EXPERT to expert,
            SkillLevel.ADVANCED to advanced,
            SkillLevel.INTERMEDIATE to intermediate,
            SkillLevel.BEGINNER to beginner,
        ),
    )

    @Test
    fun `advanced and expert may overlap while both clear intermediate`() {
        val report = evaluateCalibration(
            listOf(
                seed(1, expert = 18.0, advanced = 22.0, intermediate = -5.0, beginner = -30.0),
                seed(2, expert = 24.0, advanced = 17.0, intermediate = -8.0, beginner = -27.0),
                seed(3, expert = 16.0, advanced = 25.0, intermediate = -10.0, beginner = -35.0),
                seed(4, expert = 21.0, advanced = 19.0, intermediate = -6.0, beginner = -31.0),
            )
        )

        assertTrue(report.passes)
    }

    @Test
    fun `intermediate beating advanced is not hidden by a two-tier comparison`() {
        val report = evaluateCalibration(
            listOf(
                seed(1, expert = 30.0, advanced = 2.0, intermediate = 8.0, beginner = -30.0),
                seed(2, expert = 28.0, advanced = 3.0, intermediate = 9.0, beginner = -28.0),
                seed(3, expert = 32.0, advanced = 1.0, intermediate = 7.0, beginner = -32.0),
                seed(4, expert = 29.0, advanced = 2.0, intermediate = 8.0, beginner = -29.0),
            )
        )

        assertFalse(report.passes)
        assertFalse(report.separations.first { it.label == "Advanced > Intermediate" }.passes)
    }

    @Test
    fun `beginner beating intermediate fails calibration`() {
        val report = evaluateCalibration(
            listOf(
                seed(1, expert = 30.0, advanced = 28.0, intermediate = -8.0, beginner = 2.0),
                seed(2, expert = 31.0, advanced = 27.0, intermediate = -7.0, beginner = 3.0),
                seed(3, expert = 29.0, advanced = 26.0, intermediate = -9.0, beginner = 1.0),
                seed(4, expert = 32.0, advanced = 29.0, intermediate = -6.0, beginner = 4.0),
            )
        )

        assertFalse(report.passes)
        assertFalse(report.separations.first { it.label == "Intermediate > Beginner" }.passes)
    }

    @Test
    fun `style contracts reject a misnamed rock and passive maniac`() {
        val normal = listOf(
            StyleMetrics("Rock", 0.12, vpip = 12.0, pfr = 4.0, aggressionFactor = 0.4, aggressiveActionShare = 0.18),
            StyleMetrics("Tight-Aggressive", 0.22, vpip = 20.0, pfr = 12.0, aggressionFactor = 2.0, aggressiveActionShare = 0.22),
            StyleMetrics("Trapper", 0.28, vpip = 26.0, pfr = 9.0, aggressionFactor = 0.7, aggressiveActionShare = 0.20),
            StyleMetrics("Loose-Aggressive", 0.45, vpip = 42.0, pfr = 28.0, aggressionFactor = 2.8, aggressiveActionShare = 0.39),
            StyleMetrics("Calling Station", 0.62, vpip = 58.0, pfr = 8.0, aggressionFactor = 0.3, aggressiveActionShare = 0.12),
            StyleMetrics("Maniac", 0.75, vpip = 70.0, pfr = 45.0, aggressionFactor = 3.5, aggressiveActionShare = 0.46),
        )
        assertTrue(evaluateStyleCalibration(normal).all { it.passes })

        val broken = normal.map {
            when (it.label) {
                "Rock" -> it.copy(vpip = 31.0)
                "Maniac" -> it.copy(aggressionFactor = 0.5)
                else -> it
            }
        }
        val results = evaluateStyleCalibration(broken)
        assertFalse(results.first { it.label == "Rock remains tight" }.passes)
        assertFalse(results.first { it.label == "Maniac stays aggressive" }.passes)

        val overAggressiveStation = normal.map {
            if (it.label == "Calling Station") it.copy(pfr = 19.0) else it
        }
        assertFalse(
            evaluateStyleCalibration(overAggressiveStation)
                .first { it.label == "Calling Station rarely raises pre-flop" }
                .passes
        )
    }

    @Test
    fun `persistent-stack calibration exercises short-stack pricing`() {
        val coverage = runShortStackCoverage(
            hands = 1_000,
            seed = 20_260_729L,
            verbose = false,
        )

        assertTrue(coverage.eligiblePotDiffersFromPot > 0)
        assertTrue(coverage.callClampedByStack > 0)
        assertTrue(coverage.passes)
    }

    @Test
    fun `short-stack coverage requires both pricing paths`() {
        assertFalse(ShortStackCoverage().passes)
        assertFalse(ShortStackCoverage(10, eligiblePotDiffersFromPot = 1).passes)
        assertFalse(ShortStackCoverage(10, callClampedByStack = 1).passes)
        assertTrue(
            ShortStackCoverage(
                decisions = 10,
                eligiblePotDiffersFromPot = 1,
                callClampedByStack = 1,
            ).passes,
        )
    }
}
