package com.jsjdesigns.poker.bot

import com.jsjdesigns.poker.game.ActionType
import com.jsjdesigns.poker.game.Street
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class PersonaArbiterTest {

    private fun cue(
        actionNumber: Int = 3,
        actionType: ActionType = ActionType.RAISE,
        allIn: Boolean = true,
    ) = PersonaCue(
        id = PersonaCueId(handNumber = 2, actionNumber = actionNumber),
        street = Street.TURN,
        speakerSeat = 2,
        speakerName = "Bruno",
        persona = "Relentless pressure.",
        actionType = actionType,
        amount = 24,
        pot = 51,
        isAllIn = allIn,
        activePlayers = 3,
    )

    @Test
    fun `offline voice is deterministic for the same accepted action`() = runTest {
        val narrator = OfflinePersonaNarrator()
        val decision = cue()

        assertEquals(narrator.narrate(decision), narrator.narrate(decision))
    }

    @Test
    fun `arbiter returns bounded single-line language with authoritative identity`() = runTest {
        val arbiter = PersonaArbiter(
            primary = PersonaNarrator { "  First line\nsecond\tline with extra words  " },
            maxCharacters = 24,
        )

        val line = assertNotNull(arbiter.lineFor(cue()))

        assertEquals(PersonaCueId(2, 3), line.cueId)
        assertEquals(Street.TURN, line.street)
        assertEquals(2, line.speakerSeat)
        assertEquals("Bruno", line.speakerName)
        assertEquals("First line second line w", line.text)
        assertTrue('\n' !in line.text)
        assertTrue(line.text.length <= 24)
    }

    @Test
    fun `runtime failure and timeout both fall back offline`() = runTest {
        val failure = PersonaArbiter(
            primary = PersonaNarrator { throw IllegalStateException("network failed") },
        )
        val timeout = PersonaArbiter(
            primary = PersonaNarrator {
                delay(500)
                "too late"
            },
            timeoutMillis = 10,
        )

        assertNotNull(failure.lineFor(cue(actionNumber = 4)))
        assertNotNull(timeout.lineFor(cue(actionNumber = 5)))
    }

    @Test
    fun `narrator choosing silence is not replaced by fallback chatter`() = runTest {
        val arbiter = PersonaArbiter(
            primary = PersonaNarrator { null },
            fallback = PersonaNarrator { "fallback" },
        )

        assertEquals(null, arbiter.lineFor(cue()))
    }

    @Test
    fun `coroutine cancellation is never converted into table talk`() = runTest {
        val arbiter = PersonaArbiter(
            primary = PersonaNarrator { throw CancellationException("screen left") },
        )

        assertFailsWith<CancellationException> { arbiter.lineFor(cue()) }
    }

    @Test
    fun `rate gate is stable and all-in speech is always eligible`() {
        val ordinary = cue(allIn = false)

        assertEquals(
            shouldRequestPersonaLine(ordinary),
            shouldRequestPersonaLine(ordinary),
        )
        assertTrue(shouldRequestPersonaLine(cue(allIn = true)))
    }
}
