package com.jsjdesigns.poker

import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import com.jsjdesigns.poker.bot.CoachingReview
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext

interface PlayerHistoryStore {
    suspend fun load(): PlayerHistory
    suspend fun recordSession(playerName: String, coachEnabled: Boolean): PlayerHistory
    suspend fun recordCompletedHand(): PlayerHistory
    suspend fun recordCoachReview(review: CoachingReview): PlayerHistory
}

/**
 * Small versioned aggregate store.
 *
 * SharedPreferences is appropriate here because the complete schema is a few
 * scalar counters. Mutations are serialised and committed on Dispatchers.IO so a
 * hand completion and an asynchronous coach review cannot overwrite each other.
 */
class SharedPreferencesPlayerHistoryStore(context: Context) : PlayerHistoryStore {
    private val preferences =
        context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
    private val mutex = Mutex()

    override suspend fun load(): PlayerHistory = withContext(Dispatchers.IO) {
        mutex.withLock { preferences.readHistory() }
    }

    override suspend fun recordSession(
        playerName: String,
        coachEnabled: Boolean,
    ): PlayerHistory = mutate { it.recordSession(playerName, coachEnabled) }

    override suspend fun recordCompletedHand(): PlayerHistory =
        mutate(PlayerHistory::recordCompletedHand)

    override suspend fun recordCoachReview(review: CoachingReview): PlayerHistory =
        mutate { it.recordCoachReview(review) }

    private suspend fun mutate(block: (PlayerHistory) -> PlayerHistory): PlayerHistory =
        withContext(Dispatchers.IO) {
            mutex.withLock {
                val updated = block(preferences.readHistory())
                check(preferences.writeHistory(updated)) { "could not persist player history" }
                updated
            }
        }

    private companion object {
        const val PREFERENCES_NAME = "player_history"
        const val KEY_SCHEMA = "schema_version"
        const val KEY_PLAYER_NAME = "player_name"
        const val KEY_COACH_ENABLED = "coach_enabled"
        const val KEY_SESSIONS = "sessions_started"
        const val KEY_HANDS = "hands_completed"
        const val KEY_DECISIONS = "decisions_reviewed"
        const val KEY_ALIGNED = "baseline_aligned"

        fun SharedPreferences.readHistory(): PlayerHistory {
            val storedSchema = getInt(KEY_SCHEMA, PLAYER_HISTORY_SCHEMA_VERSION)
            require(storedSchema <= PLAYER_HISTORY_SCHEMA_VERSION) {
                "player history schema $storedSchema is newer than supported " +
                    PLAYER_HISTORY_SCHEMA_VERSION
            }
            return PlayerHistory(
                playerName = getString(KEY_PLAYER_NAME, "").orEmpty(),
                coachEnabled = getBoolean(KEY_COACH_ENABLED, false),
                sessionsStarted = getLong(KEY_SESSIONS, 0),
                handsCompleted = getLong(KEY_HANDS, 0),
                decisionsReviewed = getLong(KEY_DECISIONS, 0),
                baselineAligned = getLong(KEY_ALIGNED, 0),
                leakCounts = LeakKind.entries.associateWith { kind ->
                    getLong("leak_${kind.id}", 0)
                },
            )
        }

        @SuppressLint("UseKtx") // KTX edit discards commit(), so durability failures cannot be reported.
        fun SharedPreferences.writeHistory(history: PlayerHistory): Boolean {
            val editor = edit()
                .putInt(KEY_SCHEMA, history.schemaVersion)
                .putString(KEY_PLAYER_NAME, history.playerName)
                .putBoolean(KEY_COACH_ENABLED, history.coachEnabled)
                .putLong(KEY_SESSIONS, history.sessionsStarted)
                .putLong(KEY_HANDS, history.handsCompleted)
                .putLong(KEY_DECISIONS, history.decisionsReviewed)
                .putLong(KEY_ALIGNED, history.baselineAligned)
            for (kind in LeakKind.entries) {
                editor.putLong("leak_${kind.id}", history.leakCount(kind))
            }
            return editor.commit()
        }
    }
}
