package dev.inmo.postssystem.server

import dev.inmo.postssystem.features.auth.server.AuthenticationRoutingConfigurator
import dev.inmo.postssystem.features.auth.server.SessionAuthenticationConfigurator
import dev.inmo.postssystem.features.auth.server.tokens.*
import dev.inmo.postssystem.features.common.server.sessions.ApplicationAuthenticationConfigurator
import dev.inmo.postssystem.features.files.common.*
import dev.inmo.postssystem.features.files.common.storage.*
import dev.inmo.postssystem.features.files.common.storage.WriteFilesStorage
import dev.inmo.postssystem.features.files.server.FilesRoutingConfigurator
import dev.inmo.postssystem.features.roles.common.*
import dev.inmo.postssystem.features.roles.common.keyvalue.KeyValuesRolesOriginalRepo
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRole
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRoleStorage
import dev.inmo.postssystem.features.roles.manager.server.RolesManagerRolesChecker
import dev.inmo.postssystem.features.roles.manager.server.RolesManagerRolesStorageServerRoutesConfigurator
import dev.inmo.postssystem.features.roles.server.*
import dev.inmo.postssystem.features.status.server.StatusRoutingConfigurator
import dev.inmo.postssystem.features.users.common.ExposedUsersStorage
import dev.inmo.postssystem.features.users.server.UsersStorageServerRoutesConfigurator
import dev.inmo.micro_utils.coroutines.LinkedSupervisorScope
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.configurators.*
import dev.inmo.micro_utils.ktor.server.createKtorServer
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedOneToManyKeyValueRepo
import dev.inmo.postssystem.features.common.common.*
import dev.inmo.postssystem.features.content.binary.server.BinaryServerContentStorage
import dev.inmo.postssystem.features.content.common.*
import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage
import dev.inmo.postssystem.features.content.server.ServerContentStorageAggregator
import dev.inmo.postssystem.features.content.text.common.TextContentSerializerModuleConfigurator
import dev.inmo.postssystem.features.content.text.server.TextServerContentStorage
import dev.inmo.postssystem.features.posts.server.ExposedServerPostsStorage
import dev.inmo.postssystem.features.posts.server.ServerPostsStorage
import dev.inmo.postssystem.features.publication.server.PublicationManager
import dev.inmo.postssystem.features.publication.server.PublicationTarget
import dev.inmo.postssystem.services.posts.server.*
import dev.inmo.postssystem.targets.telegram.publication.server.PublicationTargetTelegram
import io.ktor.application.featureOrNull
import io.ktor.application.log
import io.ktor.routing.Route
import io.ktor.routing.Routing
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.netty.Netty
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.jetbrains.exposed.sql.Database
import org.koin.core.module.Module
import org.koin.core.parameter.ParametersHolder
import org.koin.core.qualifier.StringQualifier
import org.koin.dsl.*
import java.io.File

private fun Route.print() {
    println(this)
    children.forEach {
        it.print()
    }
}


fun getDIModule(
    vararg args: String,
    baseScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
): Module {
    val configJson = Json {
        ignoreUnknownKeys = true
    }
    val config = configJson.decodeFromString(Config.serializer(), File(args.first()).readText())

    val originalFilesMetasKeyValueRepoQualifier = StringQualifier("OriginalFilesMetaKV")
    val binaryOriginalFilesMetasKeyValueRepoQualifier = StringQualifier("BinaryOriginalFilesMetaKV")
    val commonFilesMetasKeyValueRepoQualifier = StringQualifier("CommonFilesMetaKV")
    val binaryFilesMetasKeyValueRepoQualifier = StringQualifier("BinaryFilesMetaKV")
    val filesFolderQualifier = StringQualifier("rootFilesFolder")
    val commonFilesFolderQualifier = StringQualifier("commonFilesFolder")
    val binaryFilesFolderQualifier = StringQualifier("binaryFilesFolder")
    val usersRolesKeyValueFactoryQualifier = StringQualifier("usersRolesKeyValueFactory")

    return 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<BinaryFormat> {
            Cbor {
                serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } }
            }
        }

        single { UnifiedRouter(get()) }

        singleWithBinds { config }
        singleWithBinds { get<Config>().databaseConfig }
        singleWithBinds { get<Config>().authConfig }
        singleWithBinds(filesFolderQualifier) { get<Config>().filesFolderFile }
        singleWithBinds(commonFilesFolderQualifier) { get<Config>().commonFilesFolder }
        singleWithBinds(binaryFilesFolderQualifier) { get<Config>().binaryFilesFolder }

        singleWithBinds { get<DatabaseConfig>().database }
        singleWithBinds(originalFilesMetasKeyValueRepoQualifier) {
            ExposedKeyValueRepo(get(), { text("fileid") }, { text("metaInfo") }, "FileIdsToMetas")
        }
        singleWithBinds(binaryOriginalFilesMetasKeyValueRepoQualifier) {
            ExposedKeyValueRepo(get(), { text("fileid") }, { text("metaInfo") }, "BinaryContentFileIdsToMetas")
        }
        singleWithBinds(commonFilesMetasKeyValueRepoQualifier) {
            MetasKeyValueRepo(
                get(),
                get(originalFilesMetasKeyValueRepoQualifier)
            )
        }
        single<ReadFilesStorage> { DiskReadFilesStorage(get(commonFilesFolderQualifier), get(commonFilesMetasKeyValueRepoQualifier)) }
        single<WriteFilesStorage> { WriteDistFilesStorage(get(commonFilesFolderQualifier), get(commonFilesMetasKeyValueRepoQualifier)) }
        single<FilesStorage> { DefaultFilesStorage(get(), get()) }
        singleWithBinds { ExposedUsersStorage(get()) }
        singleWithBinds { exposedUsersAuthenticator(get(), get()) }

        factory<KeyValuesRolesOriginalRepo>(usersRolesKeyValueFactoryQualifier) { (tableName: String) ->
            ExposedOneToManyKeyValueRepo(get(), { text("subject") }, { text("role") }, tableName)
        }
        single {
            RolesManagerRoleStorage(get(usersRolesKeyValueFactoryQualifier) { ParametersHolder(mutableListOf("rolesManager")) })
        }
        single<RolesStorage<RolesManagerRole>>(StringQualifier("RolesManagerRoleStorage")) { get<RolesManagerRoleStorage>() }
        singleWithBinds {
            RolesStorageHolder(
                RolesManagerRole::class,
                get<RolesManagerRoleStorage>()
            )
        }
        singleWithBinds<RolesStorage<Role>> { RolesAggregator(getAll()) }

        // Publication targets
        singleWithRandomQualifier<PublicationTarget> { PublicationTargetTelegram(get(), get()) }

        single { PublicationManager(getAll(), get(), get(), get()) }

        // Roles checkers
        single<RolesChecker<Role>>(StringQualifier(RolesManagerRolesChecker.key)) { RolesManagerRolesChecker }

        factory<CoroutineScope> { baseScope.LinkedSupervisorScope() }

        // Content storages
        val binaryStorageFilesQualifier = StringQualifier("binaryContentFiles")
        singleWithBinds(binaryFilesMetasKeyValueRepoQualifier) {
            MetasKeyValueRepo(
                get(),
                get(binaryOriginalFilesMetasKeyValueRepoQualifier)
            )
        }
        single<FilesStorage>(binaryStorageFilesQualifier) {
            DefaultFilesStorage(
                DiskReadFilesStorage(get(binaryFilesFolderQualifier), get(binaryFilesMetasKeyValueRepoQualifier)),
                WriteDistFilesStorage(get(binaryFilesFolderQualifier), get(binaryFilesMetasKeyValueRepoQualifier))
            )
        }
        singleWithRandomQualifier { BinaryServerContentStorage(get(binaryStorageFilesQualifier)) }
        singleWithRandomQualifier { TextServerContentStorage(get()) }

        single<ServerContentStorage<Content>> { ServerContentStorageAggregator(getAll(), get()) }

        // Posts storage
        single<ServerPostsStorage> { ExposedServerPostsStorage(get()) }

        singleWithBinds {
            ServerPostsService(
                DefaultExposedReadPostsService(get<ServerPostsStorage>()),
                DefaultWritePostsService(get<ServerPostsStorage>(), get<ServerContentStorage<Content>>())
            )
        }

        // Routing configurators
        singleWithBinds { FilesRoutingConfigurator(get(), null, get()) }
        singleWithBinds { StatusRoutingConfigurator }
        singleWithBinds { UsersStorageServerRoutesConfigurator(get(), get()) }
        singleWithBinds { RolesStorageReadServerRoutesConfigurator<Role>(get(), RoleSerializer, get()) }
        singleWithBinds { RolesManagerRolesStorageServerRoutesConfigurator(get(), get()) }
        singleWithBinds { ServerPostsServiceRoutingConfigurator(get(), get(), get()) }

        singleWithBinds { ClientStaticRoutingConfiguration("web") }
        singleWithBinds {
            RolesAuthenticationConfigurator<Role>(
                get(),
                get(),
                getAll()
            )
        }

        // Session and auth configurators
        singleWithBinds { SessionAuthenticationConfigurator(get<AuthConfig>().sessionAge) }
        singleWithBinds {
            DefaultAuthTokensService(
                get<Database>(),
                get(),
                get(),
                get<AuthConfig>().sessionAge,
                get()
            )
        }
        singleWithBinds { AuthenticationRoutingConfigurator(get(), get(), get()) }
        singleWithBinds { NotFoundStatusPageRedirectToIndex("/client") }

        if (config.debugMode) {
            single<ApplicationRoutingConfigurator.Element>(StringQualifier("Tracer")) { ApplicationRoutingConfigurator.Element {(this as Routing).trace { application.log.trace(it.buildText()) } } }
        }

        singleWithBinds { ApplicationRoutingConfigurator(getAllDistinct()) }
        singleWithBinds { ApplicationCachingHeadersConfigurator(getAllDistinct()) }
        singleWithBinds { ApplicationSessionsConfigurator(getAllDistinct()) }
        singleWithBinds { StatusPagesConfigurator(getAllDistinct()) }
        singleWithBinds { ApplicationAuthenticationConfigurator(getAllDistinct()) }
        singleWithBinds { WebSocketsConfigurator }

        factory<ApplicationEngine> {
            val config = get<Config>()
            createKtorServer(
                Netty,
                config.host,
                config.port
            ) {
                getAllDistinct<KtorApplicationConfigurator>().also(::println).forEach {
                    it.apply { configure() }
                }
                if (config.debugMode) {
                    featureOrNull(Routing) ?.print()
                }
            }
        }
    }
}
