package dev.inmo.postssystem.client

import dev.inmo.postssystem.client.ui.fsm.*
import dev.inmo.postssystem.features.auth.client.installClientAuthenticator
import dev.inmo.postssystem.features.auth.common.*
import dev.inmo.postssystem.features.files.client.ClientReadFilesStorage
import dev.inmo.postssystem.features.files.common.storage.ReadFilesStorage
import dev.inmo.postssystem.features.roles.common.Role
import dev.inmo.postssystem.features.roles.common.RolesStorage
import dev.inmo.postssystem.features.roles.client.ClientRolesStorage
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRoleSerializer
import dev.inmo.postssystem.features.users.client.UsersStorageKtorClient
import dev.inmo.postssystem.features.users.common.ReadUsersStorage
import dev.inmo.postssystem.features.users.common.User
import dev.inmo.micro_utils.common.Either
import dev.inmo.micro_utils.coroutines.LinkedSupervisorScope
import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.postssystem.client.settings.DefaultSettings
import dev.inmo.postssystem.client.settings.Settings
import dev.inmo.postssystem.client.settings.auth.AuthSettings
import dev.inmo.postssystem.client.settings.auth.DefaultAuthSettings
import dev.inmo.postssystem.features.common.common.SerializersModuleConfigurator
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
import dev.inmo.postssystem.features.content.common.OtherContentSerializerModuleConfigurator
import dev.inmo.postssystem.features.content.text.common.TextContentSerializerModuleConfigurator
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
import dev.inmo.postssystem.services.posts.client.ClientPostsService
import dev.inmo.postssystem.services.posts.common.*
import io.ktor.client.HttpClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.BinaryFormat
import kotlinx.serialization.StringFormat
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import org.koin.core.Koin
import org.koin.core.context.startKoin
import org.koin.core.module.Module
import org.koin.core.qualifier.*
import org.koin.core.scope.Scope
import org.koin.dsl.binds
import org.koin.dsl.module

val UIScopeQualifier = StringQualifier("CoroutineScopeUI")
val SettingsQualifier = StringQualifier("Settings")
val RolesQualifier = StringQualifier("Roles")
private val FSMHandlersBuilderQualifier = StringQualifier("FSMHandlersBuilder")

val defaultSerialFormat = Json {
    ignoreUnknownKeys = true
}

/**
 * Entrypoint for getting [org.koin.core.Koin] DI for the client
 *
 * @param repoFactory Factory for creating of [DefaultStatesManagerRepo] for [dev.inmo.postssystem.client.ui.fsm.UIFSM]
 */
fun baseKoin(
    defaultScope: CoroutineScope,
    settingsFactory: Scope.() -> KeyValueRepo<String, Any>,
    repoFactory: Scope.() -> DefaultStatesManagerRepo<UIFSMState>,
    handlersSetter: Pair<Scope, FSMBuilder<UIFSMState>>.() -> Unit
): Koin = startKoin {
    modules(
        module {
            singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
            singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { TextContentSerializerModuleConfigurator }
            singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
            single { SerializersModuleConfigurator(getAll()) }

            single {
                Json {
                    ignoreUnknownKeys = true
                    serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } }
                }
            }
            single<StringFormat> { get<Json>() }

            single(SettingsQualifier) { settingsFactory() }
            single { DBDropper(get(SettingsQualifier)) }
            single(FSMHandlersBuilderQualifier) { handlersSetter }
            single { repoFactory() }
            single { defaultScope }
            single(UIScopeQualifier) { get<CoroutineScope>().LinkedSupervisorScope(Dispatchers.Main) }
            single<StatesMachine<UIFSMState>>(UIFSMQualifier) { UIFSM(get()) { (this@single to this@UIFSM).apply(get(
                FSMHandlersBuilderQualifier
            )) } }
            single<AuthSettings> { DefaultAuthSettings(get(SettingsQualifier), get(), koin, get()) }
            single<Settings> { DefaultSettings(get()) }
        }
    )
}.koin.apply {
    loadModules(
        listOf(
            module { single<Koin> { this@apply } }
        )
    )
    RolesManagerRoleSerializer // Just to activate it in JS client
}

fun getAuthorizedFeaturesDIModule(
    serverUrl: String,
    initialAuthKey: Either<AuthKey, AuthTokenInfo>,
    onAuthKeyUpdated: suspend (AuthTokenInfo) -> Unit,
    onUserRetrieved: suspend (User?) -> Unit,
    onAuthKeyInvalidated: suspend () -> Unit
): Module {
    val serverUrlQualifier = StringQualifier("serverUrl")
    val credsQualifier = StringQualifier("creds")

    return module {
        single(createdAtStart = true) {
            HttpClient {
                installClientAuthenticator(serverUrl, get(), get(credsQualifier), onAuthKeyUpdated, onUserRetrieved, onAuthKeyInvalidated)
            }
        }
        single(credsQualifier) { initialAuthKey }
        single(serverUrlQualifier) { serverUrl }
        single<BinaryFormat> {
            Cbor {
                serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } }
            }
        }
        single { UnifiedRequester(get(), get()) }

        single { StatusFeatureClient(get(serverUrlQualifier), get()) }

        single<ReadFilesStorage> { ClientReadFilesStorage(get(serverUrlQualifier), get(), get()) }
        single<ReadUsersStorage> { UsersStorageKtorClient(get(serverUrlQualifier), get()) }
        single<RolesStorage<Role>> { ClientRolesStorage(get(serverUrlQualifier), get(), Role.serializer()) }
        single<PostsService> { ClientPostsService(get(serverUrlQualifier), get()) } binds arrayOf(
            ReadPostsService::class,
            WritePostsService::class
        )
    }
}
