package dev.inmo.postssystem.client.settings.auth

import dev.inmo.postssystem.client.DBDropper
import dev.inmo.postssystem.client.getAuthorizedFeaturesDIModule
import dev.inmo.postssystem.features.auth.client.AuthUnavailableException
import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.postssystem.features.auth.common.*
import dev.inmo.postssystem.features.roles.common.Role
import dev.inmo.postssystem.features.roles.common.RolesStorage
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
import dev.inmo.postssystem.features.users.common.User
import dev.inmo.micro_utils.common.Either
import dev.inmo.micro_utils.common.either
import dev.inmo.micro_utils.coroutines.plus
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.koin.core.Koin
import org.koin.core.module.Module

data class DefaultAuthSettings(
    private val repo: KeyValueRepo<String, Any>,
    private val scope: CoroutineScope,
    private val koin: Koin,
    private val dbDropper: DBDropper
) : AuthSettings {
    private val _authorizedDIModule = MutableStateFlow<Module?>(null)
    override val authorizedDIModule: StateFlow<Module?> = _authorizedDIModule.asStateFlow()
    private val _user = MutableStateFlow<User?>(null)
    override val user: StateFlow<User?> = _user.asStateFlow()
    private val _userRoles = MutableStateFlow<List<Role>>(emptyList())
    override val userRoles: StateFlow<List<Role>> = _userRoles.asStateFlow()

    private suspend fun getCurrentServerURL() = repo.get(SERVER_URL_FIELD) as? String
    private suspend fun getCurrentUsername() = repo.get(USERNAME_FIELD) as? String
    private suspend fun getCurrentToken() = repo.get(TOKEN_FIELD) as? AuthTokenInfo

    override val loadingJob: Job = scope.launch {
        val serverUrl = getCurrentServerURL() ?: return@launch
        val token = getCurrentToken() ?: return@launch
        updateModule(serverUrl, token.either())
    }

    val rolesUpdatingJob = (user + authorizedDIModule).subscribeSafelyWithoutExceptions(scope) {
        val user = user.value

        if (user == null || authorizedDIModule.value == null) {
            _userRoles.value = emptyList()
        } else {
            _userRoles.value = koin.get<RolesStorage<Role>>().getRoles(user.id)
        }
        println(user)
        println(userRoles.value)
    }

    override suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError? {
        return runCatching {
            if (getCurrentServerURL() != serverUrl || getCurrentUsername() != creds.username.string) {
                dbDropper()
            }
            repo.set(SERVER_URL_FIELD, serverUrl)
            repo.set(USERNAME_FIELD, creds.username.string)
            repo.unset(TOKEN_FIELD)
            updateModule(serverUrl, creds.either())
        }.onFailure {
            it.printStackTrace()
        }.getOrThrow()
    }

    private suspend fun updateModule(
        serverUrl: String,
        initialAuthKey: Either<AuthKey, AuthTokenInfo>,
    ): AuthUIError? {
        val currentModule = authorizedDIModule.value
        val newModule = getAuthorizedFeaturesDIModule(
            serverUrl,
            initialAuthKey,
            {
                repo.set(TOKEN_FIELD, it)
            },
            {
                _user.value = it
            }
        ) {
            repo.unset(SERVER_URL_FIELD, USERNAME_FIELD, TOKEN_FIELD)
            _authorizedDIModule.value = null
            _user.value = null
            throw AuthUnavailableException
        }
        currentModule ?.let { koin.unloadModules(listOf(currentModule)) }
        koin.loadModules(listOf(newModule))
        val statusFeature = koin.get<StatusFeatureClient>()

        val serverAvailable = statusFeature.checkServerStatus()
        val authCorrect = serverAvailable && runCatching {
            statusFeature.checkServerStatusWithAuth()
        }.getOrElse { false }
        if (!serverAvailable && !authCorrect) {
            koin.unloadModules(listOf(newModule))
            currentModule ?.let { koin.loadModules(listOf(currentModule)) }
        }
        return when {
            !serverAvailable -> ServerUnavailableAuthUIError
            !authCorrect -> AuthIncorrectAuthUIError
            else -> {
                _authorizedDIModule.value = newModule
                null
            }
        }
    }

    companion object {
        private const val SERVER_URL_FIELD = "AuthServerURL"
        private const val USERNAME_FIELD = "AuthUsername"
        private const val TOKEN_FIELD = "AuthToken"
    }
}
