package com.jsjdesigns.poker

import com.jsjdesigns.poker.bot.CoachingReview
import com.jsjdesigns.poker.bot.DecisionTrace
import com.jsjdesigns.poker.game.Action
import com.jsjdesigns.poker.game.ActionType
import com.jsjdesigns.poker.game.Street
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test

class PlayerHistoryTest {

    private fun review(
        street: Street,
        intended: ActionType,
        chosen: ActionType,
        aligned: Boolean = false,
    ) = CoachingReview(
        decisionToken = 1,
        handNumber = 1,
        street = street,
        trace = DecisionTrace(
            estimatedEquity = null,
            handStrengthPercentile = null,
            breakEvenEquity = null,
            decisionThreshold = null,
            preflopRangeThreshold = null,
            potOdds = null,
            intended = Action(intended),
            chosen = Action(chosen),
            mistakeApplied = false,
            adjustments = emptyList(),
            reason = "test",
        ),
        alignedWithBaseline = aligned,
    )

    @Test
    fun `every strategic disagreement has one stable aggregate category`() {
        val cases = listOf(
            Triple(Street.PREFLOP, ActionType.FOLD to ActionType.CALL, LeakKind.PREFLOP_TOO_LOOSE),
            Triple(Street.PREFLOP, ActionType.CALL to ActionType.FOLD, LeakKind.PREFLOP_TOO_TIGHT),
            Triple(Street.PREFLOP, ActionType.RAISE to ActionType.CALL, LeakKind.PREFLOP_MISSED_RAISE),
            Triple(Street.PREFLOP, ActionType.CALL to ActionType.RAISE, LeakKind.PREFLOP_OVERAGGRESSION),
            Triple(Street.RIVER, ActionType.FOLD to ActionType.CALL, LeakKind.POSTFLOP_LOOSE_CONTINUE),
            Triple(Street.RIVER, ActionType.CALL to ActionType.FOLD, LeakKind.POSTFLOP_OVERFOLD),
            Triple(Street.RIVER, ActionType.BET to ActionType.CHECK, LeakKind.POSTFLOP_MISSED_AGGRESSION),
            Triple(Street.RIVER, ActionType.CHECK to ActionType.BET, LeakKind.POSTFLOP_OVERAGGRESSION),
        )

        for ((street, actions, expected) in cases) {
            assertEquals(
                expected,
                classifyLeak(review(street, actions.first, actions.second)),
            )
        }
    }

    @Test
    fun `persisted leak identifiers are unique and version one stable`() {
        assertEquals(1, PLAYER_HISTORY_SCHEMA_VERSION)
        assertEquals(
            listOf(
                "preflop_too_loose",
                "preflop_too_tight",
                "preflop_missed_raise",
                "preflop_overaggression",
                "postflop_loose_continue",
                "postflop_overfold",
                "postflop_missed_aggression",
                "postflop_overaggression",
            ),
            LeakKind.entries.map(LeakKind::id),
        )
        assertEquals(LeakKind.entries.size, LeakKind.entries.map(LeakKind::id).toSet().size)
    }

    @Test
    fun `aligned decisions improve agreement without inventing a leak`() {
        val aligned = review(
            street = Street.FLOP,
            intended = ActionType.BET,
            chosen = ActionType.RAISE,
            aligned = true,
        )
        val history = PlayerHistory().recordCoachReview(aligned)

        assertEquals(1L, history.decisionsReviewed)
        assertEquals(1L, history.baselineAligned)
        assertTrue(history.leakCounts.isEmpty())
        assertNull(classifyLeak(aligned))
    }

    @Test
    fun `session preferences hands and reviews aggregate without raw hand data`() {
        val looseCall = review(Street.TURN, ActionType.FOLD, ActionType.CALL)
        val history = PlayerHistory()
            .recordSession("Jay", coachEnabled = true)
            .recordCompletedHand()
            .recordCompletedHand()
            .recordCoachReview(looseCall)
            .recordCoachReview(looseCall)

        assertEquals("Jay", history.playerName)
        assertTrue(history.coachEnabled)
        assertEquals(1L, history.sessionsStarted)
        assertEquals(2L, history.handsCompleted)
        assertEquals(2L, history.decisionsReviewed)
        assertEquals(2L, history.leakCount(LeakKind.POSTFLOP_LOOSE_CONTINUE))
    }

    @Test
    fun `history refuses to call a small sample a trend`() {
        val history = PlayerHistory(
            sessionsStarted = 1,
            handsCompleted = 4,
            decisionsReviewed = 3,
            baselineAligned = 1,
            leakCounts = mapOf(LeakKind.POSTFLOP_OVERFOLD to 2),
        )
        val presentation = historyPresentation(history)!!

        assertEquals("4 hands saved • 3 coached decisions", presentation.headline)
        assertTrue("7 more coached decisions" in presentation.detail)
        assertTrue("trend" in presentation.detail)
    }

    @Test
    fun `mature history reports agreement and the most repeated review`() {
        val history = PlayerHistory(
            sessionsStarted = 3,
            handsCompleted = 42,
            decisionsReviewed = 20,
            baselineAligned = 13,
            leakCounts = mapOf(
                LeakKind.PREFLOP_TOO_LOOSE to 3,
                LeakKind.POSTFLOP_OVERFOLD to 4,
            ),
        )
        val presentation = historyPresentation(history)!!

        assertTrue("Baseline agreement 65%" in presentation.detail)
        assertTrue(LeakKind.POSTFLOP_OVERFOLD.label in presentation.detail)
        assertTrue("(4)" in presentation.detail)
    }

    @Test
    fun `brand new player has no fake history card`() {
        assertNull(historyPresentation(PlayerHistory()))
    }
}
