package com.jsjdesigns.poker

import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class PlayerHistoryStoreTest {

    private class FakePersistence(
        var stored: PlayerHistory = PlayerHistory(),
        var readFailure: RuntimeException? = null,
        var writeResult: Boolean = true,
        var writeFailure: RuntimeException? = null,
    ) : PlayerHistoryPersistence {
        var reads = 0
        var writes = 0

        override fun read(): PlayerHistory {
            reads++
            readFailure?.let { throw it }
            return stored
        }

        override fun write(history: PlayerHistory): Boolean {
            writes++
            writeFailure?.let { throw it }
            if (writeResult) stored = history
            return writeResult
        }
    }

    @Test
    fun `schema dispatch has an explicit migration branch`() {
        assertEquals(HistorySchemaPlan.FRESH, historySchemaPlan(0))
        assertEquals(HistorySchemaPlan.READ_VERSION_1, historySchemaPlan(1))
        assertEquals(HistorySchemaPlan.UNSUPPORTED, historySchemaPlan(2))
        assertEquals(HistorySchemaPlan.UNSUPPORTED, historySchemaPlan(99))
        assertEquals(HistorySchemaPlan.UNSUPPORTED, historySchemaPlan(-1))
    }

    @Test
    fun `newer schema falls back in memory without overwrite or crash`() = runTest {
        val persistence = FakePersistence(
            readFailure = UnsupportedPlayerHistorySchema(99),
        )
        val failures = mutableListOf<String>()
        val store = ResilientPlayerHistoryStore(persistence) { message, _ ->
            failures += message
        }

        assertEquals(PlayerHistory(), store.load())
        val currentSession = store.recordSession("Jay", coachEnabled = true)

        assertEquals("Jay", currentSession.playerName)
        assertTrue(currentSession.coachEnabled)
        assertEquals(1L, currentSession.sessionsStarted)
        assertEquals("fallback remains useful in this process", currentSession, store.load())
        assertEquals(1, persistence.reads)
        assertEquals("an older build must not overwrite newer data", 0, persistence.writes)
        assertEquals(1, failures.size)
    }

    @Test
    fun `corrupt history also degrades to an in-memory session`() = runTest {
        val persistence = FakePersistence(
            readFailure = IllegalArgumentException("corrupt counter"),
        )
        val store = ResilientPlayerHistoryStore(persistence)

        assertEquals(PlayerHistory(), store.load())
        assertEquals(1L, store.recordCompletedHand().handsCompleted)
        assertEquals(0, persistence.writes)
    }

    @Test
    fun `failed commit disables further writes but keeps current counters`() = runTest {
        val persistence = FakePersistence(writeResult = false)
        val failures = mutableListOf<String>()
        val store = ResilientPlayerHistoryStore(persistence) { message, _ ->
            failures += message
        }

        assertEquals(1L, store.recordCompletedHand().handsCompleted)
        assertEquals(2L, store.recordCompletedHand().handsCompleted)

        assertEquals("do not repeatedly hammer failed storage", 1, persistence.writes)
        assertEquals(1, failures.size)
        assertTrue("commit" in failures.single())
    }

    @Test
    fun `write exception is contained like a false commit`() = runTest {
        val persistence = FakePersistence(
            writeFailure = IllegalStateException("disk unavailable"),
        )
        val store = ResilientPlayerHistoryStore(persistence)

        assertEquals(1L, store.recordCompletedHand().handsCompleted)
        assertEquals(2L, store.recordCompletedHand().handsCompleted)
        assertEquals(1, persistence.writes)
    }

    @Test
    fun `concurrent hand updates cannot clobber one another`() = runTest {
        val persistence = FakePersistence()
        val store = ResilientPlayerHistoryStore(persistence)

        coroutineScope {
            repeat(100) {
                launch { store.recordCompletedHand() }
            }
        }

        assertEquals(100L, store.load().handsCompleted)
        assertEquals(100, persistence.writes)
        assertFalse(persistence.stored.coachEnabled)
    }
}
