package cy.hexor.furumi.data.local

import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import cy.hexor.furumi.domain.model.AuthSession
import cy.hexor.furumi.domain.model.AuthTokens
import cy.hexor.furumi.domain.model.User
import com.squareup.moshi.Moshi

class AuthSessionStorage(context: Context, moshi: Moshi) {
    private val masterKey = MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

    private val prefs = EncryptedSharedPreferences.create(
        context,
        PREFS_NAME,
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

    private val userAdapter = moshi.adapter(User::class.java)

    fun saveSession(session: AuthSession) {
        prefs.edit()
            .putString(KEY_BASE_URL, session.serverBaseUrl)
            .putString(KEY_ACCESS_TOKEN, session.tokens.accessToken)
            .putString(KEY_REFRESH_TOKEN, session.tokens.refreshToken)
            .putString(KEY_TOKEN_TYPE, session.tokens.tokenType)
            .putInt(KEY_EXPIRES_IN_SECONDS, session.tokens.expiresInSeconds)
            .putLong(KEY_EXPIRES_AT_EPOCH_SECONDS, session.tokens.expiresAtEpochSeconds)
            .putString(KEY_USER_DATA, userAdapter.toJson(session.user))
            .remove(KEY_PENDING_SSO_BASE_URL)
            .apply()
    }

    fun saveTokens(tokens: AuthTokens) {
        prefs.edit()
            .putString(KEY_ACCESS_TOKEN, tokens.accessToken)
            .putString(KEY_REFRESH_TOKEN, tokens.refreshToken)
            .putString(KEY_TOKEN_TYPE, tokens.tokenType)
            .putInt(KEY_EXPIRES_IN_SECONDS, tokens.expiresInSeconds)
            .putLong(KEY_EXPIRES_AT_EPOCH_SECONDS, tokens.expiresAtEpochSeconds)
            .apply()
    }

    fun savePendingSsoBaseUrl(baseUrl: String) {
        prefs.edit()
            .putString(KEY_PENDING_SSO_BASE_URL, baseUrl)
            .apply()
    }

    fun getPendingSsoBaseUrl(): String? {
        return prefs.getString(KEY_PENDING_SSO_BASE_URL, null)
    }

    fun getBaseUrl(): String? {
        return prefs.getString(KEY_BASE_URL, null)
    }

    fun getSession(): AuthSession? {
        val baseUrl = getBaseUrl() ?: return null
        val user = getUser() ?: return null
        val tokens = getTokens() ?: return null
        return AuthSession(baseUrl, user, tokens)
    }

    fun getTokens(): AuthTokens? {
        val accessToken = prefs.getString(KEY_ACCESS_TOKEN, null) ?: return null
        val refreshToken = prefs.getString(KEY_REFRESH_TOKEN, null) ?: return null
        val tokenType = prefs.getString(KEY_TOKEN_TYPE, AuthTokens.DEFAULT_TOKEN_TYPE)
            ?: AuthTokens.DEFAULT_TOKEN_TYPE

        return AuthTokens(
            accessToken = accessToken,
            refreshToken = refreshToken,
            tokenType = tokenType,
            expiresInSeconds = prefs.getInt(KEY_EXPIRES_IN_SECONDS, 0),
            expiresAtEpochSeconds = prefs.getLong(KEY_EXPIRES_AT_EPOCH_SECONDS, 0L)
        )
    }

    private fun getUser(): User? {
        val json = prefs.getString(KEY_USER_DATA, null) ?: return null
        return runCatching { userAdapter.fromJson(json) }.getOrNull()
    }

    fun clear() {
        prefs.edit().clear().apply()
    }

    fun clearSession() {
        prefs.edit()
            .remove(KEY_ACCESS_TOKEN)
            .remove(KEY_REFRESH_TOKEN)
            .remove(KEY_TOKEN_TYPE)
            .remove(KEY_EXPIRES_IN_SECONDS)
            .remove(KEY_EXPIRES_AT_EPOCH_SECONDS)
            .remove(KEY_PENDING_SSO_BASE_URL)
            .remove(KEY_USER_DATA)
            .apply()
    }

    companion object {
        private const val PREFS_NAME = "auth_session"
        private const val KEY_ACCESS_TOKEN = "access_token"
        private const val KEY_REFRESH_TOKEN = "refresh_token"
        private const val KEY_TOKEN_TYPE = "token_type"
        private const val KEY_EXPIRES_IN_SECONDS = "expires_in_seconds"
        private const val KEY_EXPIRES_AT_EPOCH_SECONDS = "expires_at_epoch_seconds"
        private const val KEY_BASE_URL = "base_url"
        private const val KEY_PENDING_SSO_BASE_URL = "pending_sso_base_url"
        private const val KEY_USER_DATA = "user_data"
    }
}
