package com.jsjdesigns.poker

import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
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
}

internal interface PlayerHistoryPersistence {
    fun read(): PlayerHistory
    fun write(history: PlayerHistory): Boolean
}

/**
 * Serialised read-modify-write with a deliberately non-fatal failure policy.
 *
 * History is coaching metadata, never game-critical state. If data is corrupt,
 * belongs to a newer app schema, or cannot be written, the current process keeps
 * a fresh in-memory history and stops touching durable storage. In particular,
 * an older sideloaded APK neither crashes nor overwrites a newer schema.
 */
internal class ResilientPlayerHistoryStore(
    private val persistence: PlayerHistoryPersistence,
    private val failureReporter: (message: String, error: RuntimeException?) -> Unit = { _, _ -> },
) : PlayerHistoryStore {
    private val mutex = Mutex()
    private var cachedHistory: PlayerHistory? = null
    private var persistenceAvailable = true

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

    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(currentHistory())
                cachedHistory = updated
                if (persistenceAvailable) {
                    try {
                        if (!persistence.write(updated)) {
                            disablePersistence("Could not commit player history", null)
                        }
                    } catch (error: RuntimeException) {
                        disablePersistence("Could not write player history", error)
                    }
                }
                updated
            }
        }

    private fun currentHistory(): PlayerHistory {
        cachedHistory?.let { return it }
        val loaded = if (persistenceAvailable) {
            try {
                persistence.read()
            } catch (error: RuntimeException) {
                disablePersistence("Could not read player history", error)
                PlayerHistory()
            }
        } else {
            PlayerHistory()
        }
        cachedHistory = loaded
        return loaded
    }

    private fun disablePersistence(message: String, error: RuntimeException?) {
        persistenceAvailable = false
        // Reporting must not turn a handled storage failure back into a crash.
        runCatching { failureReporter(message, error) }
    }
}

class SharedPreferencesPlayerHistoryStore(context: Context) : PlayerHistoryStore by
    ResilientPlayerHistoryStore(
        persistence = SharedPreferencesHistoryPersistence(
            context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE),
        ),
        failureReporter = { message, error ->
            if (error == null) Log.e(LOG_TAG, message) else Log.e(LOG_TAG, message, error)
        },
    )

internal enum class HistorySchemaPlan {
    FRESH,
    READ_VERSION_1,
    UNSUPPORTED,
}

/**
 * Explicit migration dispatch. Add a branch and migration before incrementing
 * [PLAYER_HISTORY_SCHEMA_VERSION]; never reinterpret a non-additive schema.
 */
internal fun historySchemaPlan(storedVersion: Int): HistorySchemaPlan = when (storedVersion) {
    0 -> HistorySchemaPlan.FRESH
    1 -> HistorySchemaPlan.READ_VERSION_1
    else -> HistorySchemaPlan.UNSUPPORTED
}

internal class UnsupportedPlayerHistorySchema(storedVersion: Int) :
    IllegalStateException(
        "player history schema $storedVersion is not supported by schema " +
            PLAYER_HISTORY_SCHEMA_VERSION,
    )

private class SharedPreferencesHistoryPersistence(
    private val preferences: SharedPreferences,
) : PlayerHistoryPersistence {
    override fun read(): PlayerHistory {
        val storedSchema = if (preferences.contains(KEY_SCHEMA)) {
            preferences.getInt(KEY_SCHEMA, 0)
        } else {
            0
        }
        return when (historySchemaPlan(storedSchema)) {
            HistorySchemaPlan.FRESH -> PlayerHistory()
            HistorySchemaPlan.READ_VERSION_1 -> preferences.readVersion1()
            HistorySchemaPlan.UNSUPPORTED ->
                throw UnsupportedPlayerHistorySchema(storedSchema)
        }
    }

    @SuppressLint("UseKtx") // KTX edit discards commit(), so failures cannot be reported.
    override fun write(history: PlayerHistory): Boolean {
        val editor = preferences.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()
    }

    private fun SharedPreferences.readVersion1(): PlayerHistory = 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)
        },
    )
}

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