package com.fabledsword.minstrel.auth

import com.fabledsword.minstrel.cache.audiocache.CacheSettings
import com.fabledsword.minstrel.cache.db.dao.AuthSessionDao
import com.fabledsword.minstrel.cache.db.entities.AuthSessionEntity
import com.fabledsword.minstrel.di.ApplicationScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Holds the user's session cookie and configured server base URL.
 *
 * **Hybrid storage**: a `MutableStateFlow` is the primary read source so
 * interceptor-thread reads stay synchronous, and writes are persisted
 * write-through to a Room `auth_session` single-row table for
 * process-death persistence.
 *
 * Reads on init() rehydrate the in-memory state from the DAO, then a
 * collection on `dao.observe()` keeps the in-memory state in sync with
 * any external writes (e.g. multi-process pokes, future). On mutate the
 * in-memory state changes synchronously so the next interceptor read
 * sees the new value immediately; the DAO write coroutine catches up
 * shortly after.
 */
// AuthStore is the single-row facade over auth_session (de-facto
// app_preferences — see entity comment). It legitimately owns one
// getter + one setter + one persist helper per pref column; the
// function count scales with the pref count, not with feature
// complexity. Splitting into per-pref stores would scatter the
// shared dao + scope + json plumbing for no real gain. Suppress
// the cap rather than fragment.
@Suppress("TooManyFunctions")
@Singleton
class AuthStore @Inject constructor(
    private val dao: AuthSessionDao,
    @ApplicationScope private val scope: CoroutineScope,
) {
    private val sessionCookieState = MutableStateFlow<String?>(null)
    val sessionCookie: StateFlow<String?> = sessionCookieState.asStateFlow()

    private val baseUrlState = MutableStateFlow(DEFAULT_BASE_URL)
    val baseUrl: StateFlow<String> = baseUrlState.asStateFlow()

    private val userJsonState = MutableStateFlow<String?>(null)
    val userJson: StateFlow<String?> = userJsonState.asStateFlow()

    private val themeModeState = MutableStateFlow<String?>(null)
    val themeMode: StateFlow<String?> = themeModeState.asStateFlow()

    private val clientIdState = MutableStateFlow<String?>(null)
    val clientId: StateFlow<String?> = clientIdState.asStateFlow()

    private val cacheSettingsState = MutableStateFlow(CacheSettings.DEFAULT)
    val cacheSettings: StateFlow<CacheSettings> = cacheSettingsState.asStateFlow()

    private val diagnosticsOptOutState = MutableStateFlow(false)
    val diagnosticsOptOut: StateFlow<Boolean> = diagnosticsOptOutState.asStateFlow()

    private val json = Json { ignoreUnknownKeys = true }

    init {
        scope.launch {
            dao.observe().collect { row ->
                sessionCookieState.value = row?.sessionCookie
                baseUrlState.value = row?.baseUrl ?: DEFAULT_BASE_URL
                userJsonState.value = row?.userJson
                themeModeState.value = row?.themeMode
                clientIdState.value = row?.clientId
                cacheSettingsState.value = decodeCacheSettings(row?.cacheSettingsJson)
                diagnosticsOptOutState.value = row?.diagnosticsOptOut ?: false
            }
        }
    }

    private fun decodeCacheSettings(raw: String?): CacheSettings {
        if (raw.isNullOrEmpty()) return CacheSettings.DEFAULT
        return runCatching {
            json.decodeFromString(CacheSettings.serializer(), raw)
        }.getOrDefault(CacheSettings.DEFAULT)
    }

    fun setSessionCookie(value: String?) {
        sessionCookieState.value = value
        scope.launch { persistCookie(value) }
    }

    fun setBaseUrl(value: String) {
        baseUrlState.value = value
        scope.launch { persistBaseUrl(value) }
    }

    fun setUserJson(value: String?) {
        userJsonState.value = value
        scope.launch { persistUserJson(value) }
    }

    fun setThemeMode(value: String?) {
        themeModeState.value = value
        scope.launch { persistThemeMode(value) }
    }

    fun setClientId(value: String?) {
        clientIdState.value = value
        scope.launch { persistClientId(value) }
    }

    fun setCacheSettings(value: CacheSettings) {
        cacheSettingsState.value = value
        val encoded = json.encodeToString(CacheSettings.serializer(), value)
        scope.launch { persistCacheSettings(encoded) }
    }

    fun setDiagnosticsOptOut(value: Boolean) {
        diagnosticsOptOutState.value = value
        scope.launch { persistDiagnosticsOptOut(value) }
    }

    private suspend fun persistCookie(value: String?) {
        if (dao.get() == null) {
            dao.upsert(currentEntity().copy(sessionCookie = value))
        } else {
            dao.setSessionCookie(value)
        }
    }

    private suspend fun persistBaseUrl(value: String) {
        if (dao.get() == null) {
            dao.upsert(currentEntity().copy(baseUrl = value))
        } else {
            dao.setBaseUrl(value)
        }
    }

    private suspend fun persistUserJson(value: String?) {
        if (dao.get() == null) {
            dao.upsert(currentEntity().copy(userJson = value))
        } else {
            dao.setUserJson(value)
        }
    }

    private suspend fun persistThemeMode(value: String?) {
        if (dao.get() == null) {
            dao.upsert(currentEntity().copy(themeMode = value))
        } else {
            dao.setThemeMode(value)
        }
    }

    private suspend fun persistClientId(value: String?) {
        if (dao.get() == null) {
            dao.upsert(currentEntity().copy(clientId = value))
        } else {
            dao.setClientId(value)
        }
    }

    private suspend fun persistCacheSettings(json: String) {
        if (dao.get() == null) {
            dao.upsert(currentEntity().copy(cacheSettingsJson = json))
        } else {
            dao.setCacheSettingsJson(json)
        }
    }

    private suspend fun persistDiagnosticsOptOut(value: Boolean) {
        if (dao.get() == null) {
            dao.upsert(currentEntity().copy(diagnosticsOptOut = value))
        } else {
            dao.setDiagnosticsOptOut(value)
        }
    }

    private fun currentEntity(): AuthSessionEntity = AuthSessionEntity(
        id = ROW_ID,
        sessionCookie = sessionCookieState.value,
        baseUrl = baseUrlState.value,
        userJson = userJsonState.value,
        themeMode = themeModeState.value,
        clientId = clientIdState.value,
        cacheSettingsJson = json.encodeToString(
            CacheSettings.serializer(),
            cacheSettingsState.value,
        ),
        diagnosticsOptOut = diagnosticsOptOutState.value,
    )

    companion object {
        const val DEFAULT_BASE_URL: String = "http://localhost:8080"
        private const val ROW_ID = 0
    }
}
