package com.bytelyst.platform

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
 * Auth client for platform-service.
 *
 * Manages login, register, token refresh, password operations.
 * Tokens are stored in [BLSecureStore] (EncryptedSharedPreferences).
 * Auth state is exposed as a [StateFlow] for reactive UI binding.
 *
 * Mirrors the Swift BLAuthClient API.
 */
class BLAuthClient(
    private val config: BLPlatformConfig,
    private val secureStore: BLSecureStore,
) {
    // ── Data classes ──────────────────────────────────────────

    @Serializable
    data class AuthUser(
        val id: String = "",
        val email: String = "",
        @SerialName("displayName")
        val name: String = "",
        val plan: String = "free",
        val role: String = "user",
    )

    @Serializable
    data class TokenResponse(
        val accessToken: String,
        val refreshToken: String,
        val user: AuthUser,
    )

    @Serializable
    data class RefreshResponse(
        val accessToken: String,
        val refreshToken: String,
    )

    @Serializable
    data class MessageResponse(val message: String)

    sealed class AuthState {
        data object Loading : AuthState()
        data object LoggedOut : AuthState()
        data class LoggedIn(val user: AuthUser) : AuthState()
        data class Error(val message: String) : AuthState()
    }

    // ── State ────────────────────────────────────────────────

    private val _state = MutableStateFlow<AuthState>(AuthState.Loading)
    val state: StateFlow<AuthState> = _state.asStateFlow()

    val isLoggedIn: Boolean
        get() = _state.value is AuthState.LoggedIn

    val currentUser: AuthUser?
        get() = (_state.value as? AuthState.LoggedIn)?.user

    // ── Storage keys (bare — applicationId provides namespace) ──

    companion object {
        private const val KEY_ACCESS_TOKEN = "access_token"
        private const val KEY_REFRESH_TOKEN = "refresh_token"
        private const val KEY_USER_EMAIL = "user_email"
        private const val KEY_USER_NAME = "user_name"
        private const val KEY_USER_PLAN = "user_plan"
        private const val KEY_USER_ID = "user_id"
    }

    // ── Platform client ──────────────────────────────────────

    private val client = BLPlatformClient(config) { getAccessToken() }

    // ── Token management ─────────────────────────────────────

    fun getAccessToken(): String? = secureStore.read(KEY_ACCESS_TOKEN)

    fun getRefreshToken(): String? = secureStore.read(KEY_REFRESH_TOKEN)

    private fun setTokens(accessToken: String, refreshToken: String) {
        secureStore.save(KEY_ACCESS_TOKEN, accessToken)
        secureStore.save(KEY_REFRESH_TOKEN, refreshToken)
    }

    private fun saveUser(user: AuthUser) {
        secureStore.save(KEY_USER_ID, user.id)
        secureStore.save(KEY_USER_EMAIL, user.email)
        secureStore.save(KEY_USER_NAME, user.name)
        secureStore.save(KEY_USER_PLAN, user.plan)
    }

    private fun clearAll() {
        secureStore.delete(KEY_ACCESS_TOKEN)
        secureStore.delete(KEY_REFRESH_TOKEN)
        secureStore.delete(KEY_USER_EMAIL)
        secureStore.delete(KEY_USER_NAME)
        secureStore.delete(KEY_USER_PLAN)
        secureStore.delete(KEY_USER_ID)
    }

    // ── Session check ────────────────────────────────────────

    /**
     * Check for an existing session from stored tokens.
     * Call once at app startup to restore auth state.
     */
    fun checkExistingSession() {
        val token = secureStore.read(KEY_ACCESS_TOKEN)
        val email = secureStore.read(KEY_USER_EMAIL)
        if (!token.isNullOrBlank() && !email.isNullOrBlank()) {
            val user = AuthUser(
                id = secureStore.read(KEY_USER_ID) ?: "",
                email = email,
                name = secureStore.read(KEY_USER_NAME) ?: "",
                plan = secureStore.read(KEY_USER_PLAN) ?: "free",
            )
            _state.value = AuthState.LoggedIn(user)
        } else {
            _state.value = AuthState.LoggedOut
        }
    }

    // ── Auth operations ──────────────────────────────────────

    suspend fun login(email: String, password: String) {
        _state.value = AuthState.Loading
        try {
            val body = client.json.encodeToString(
                kotlinx.serialization.builtins.MapSerializer(
                    kotlinx.serialization.builtins.serializer<String>(),
                    kotlinx.serialization.builtins.serializer<String>(),
                ),
                mapOf("email" to email, "password" to password, "productId" to config.productId),
            )
            val response = client.request("POST", "/api/auth/login", body, skipAuth = true)
            val result = client.json.decodeFromString<TokenResponse>(response)
            handleAuthResult(result)
        } catch (e: Exception) {
            _state.value = AuthState.Error(e.message ?: "Login failed")
        }
    }

    suspend fun register(name: String, email: String, password: String) {
        _state.value = AuthState.Loading
        try {
            val body = client.json.encodeToString(
                kotlinx.serialization.builtins.MapSerializer(
                    kotlinx.serialization.builtins.serializer<String>(),
                    kotlinx.serialization.builtins.serializer<String>(),
                ),
                mapOf(
                    "email" to email,
                    "displayName" to name,
                    "password" to password,
                    "productId" to config.productId,
                ),
            )
            val response = client.request("POST", "/api/auth/register", body, skipAuth = true)
            val result = client.json.decodeFromString<TokenResponse>(response)
            handleAuthResult(result)
        } catch (e: Exception) {
            _state.value = AuthState.Error(e.message ?: "Registration failed")
        }
    }

    fun logout() {
        clearAll()
        _state.value = AuthState.LoggedOut
    }

    // ── Token refresh (singleton guard) ──────────────────────

    private val refreshMutex = Mutex()

    suspend fun refreshAccessToken(): Boolean = refreshMutex.withLock {
        val rt = getRefreshToken() ?: return false
        return try {
            val body = client.json.encodeToString(
                kotlinx.serialization.builtins.MapSerializer(
                    kotlinx.serialization.builtins.serializer<String>(),
                    kotlinx.serialization.builtins.serializer<String>(),
                ),
                mapOf("refreshToken" to rt),
            )
            val response = client.request("POST", "/api/auth/refresh", body, skipAuth = true)
            val result = client.json.decodeFromString<RefreshResponse>(response)
            setTokens(result.accessToken, result.refreshToken)
            true
        } catch (e: BLApiException) {
            if (e.statusCode == 401) {
                withContext(Dispatchers.Main) { logout() }
            }
            false
        } catch (_: Exception) {
            false
        }
    }

    // ── Password management ──────────────────────────────────

    suspend fun forgotPassword(email: String): String {
        val body = client.json.encodeToString(
            kotlinx.serialization.builtins.MapSerializer(
                kotlinx.serialization.builtins.serializer<String>(),
                kotlinx.serialization.builtins.serializer<String>(),
            ),
            mapOf("email" to email, "productId" to config.productId),
        )
        val response = client.request("POST", "/api/auth/forgot-password", body, skipAuth = true)
        return client.json.decodeFromString<MessageResponse>(response).message
    }

    suspend fun resetPassword(token: String, newPassword: String): String {
        val body = client.json.encodeToString(
            kotlinx.serialization.builtins.MapSerializer(
                kotlinx.serialization.builtins.serializer<String>(),
                kotlinx.serialization.builtins.serializer<String>(),
            ),
            mapOf("token" to token, "newPassword" to newPassword),
        )
        val response = client.request("POST", "/api/auth/reset-password", body, skipAuth = true)
        return client.json.decodeFromString<MessageResponse>(response).message
    }

    suspend fun changePassword(currentPassword: String, newPassword: String): String {
        val body = client.json.encodeToString(
            kotlinx.serialization.builtins.MapSerializer(
                kotlinx.serialization.builtins.serializer<String>(),
                kotlinx.serialization.builtins.serializer<String>(),
            ),
            mapOf("currentPassword" to currentPassword, "newPassword" to newPassword),
        )
        val response = client.request("POST", "/api/auth/change-password", body)
        return client.json.decodeFromString<MessageResponse>(response).message
    }

    // ── Email verification ───────────────────────────────────

    suspend fun verifyEmail(token: String): String {
        val body = client.json.encodeToString(
            kotlinx.serialization.builtins.MapSerializer(
                kotlinx.serialization.builtins.serializer<String>(),
                kotlinx.serialization.builtins.serializer<String>(),
            ),
            mapOf("token" to token),
        )
        val response = client.request("POST", "/api/auth/verify-email", body)
        return client.json.decodeFromString<MessageResponse>(response).message
    }

    suspend fun resendVerification(email: String): String {
        val body = client.json.encodeToString(
            kotlinx.serialization.builtins.MapSerializer(
                kotlinx.serialization.builtins.serializer<String>(),
                kotlinx.serialization.builtins.serializer<String>(),
            ),
            mapOf("email" to email, "productId" to config.productId),
        )
        val response = client.request("POST", "/api/auth/resend-verification", body)
        return client.json.decodeFromString<MessageResponse>(response).message
    }

    // ── Account management ───────────────────────────────────

    suspend fun deleteAccount(password: String): String {
        val body = client.json.encodeToString(
            kotlinx.serialization.builtins.MapSerializer(
                kotlinx.serialization.builtins.serializer<String>(),
                kotlinx.serialization.builtins.serializer<String>(),
            ),
            mapOf("password" to password),
        )
        val response = client.request("DELETE", "/api/auth/account", body)
        clearAll()
        _state.value = AuthState.LoggedOut
        return client.json.decodeFromString<MessageResponse>(response).message
    }

    suspend fun getMe(): AuthUser {
        val response = client.request("GET", "/api/auth/me")
        return client.json.decodeFromString<AuthUser>(response)
    }

    // ── Private ──────────────────────────────────────────────

    private fun handleAuthResult(result: TokenResponse) {
        setTokens(result.accessToken, result.refreshToken)
        saveUser(result.user)
        _state.value = AuthState.LoggedIn(result.user)
    }
}
