package dev.inmo.postssystem.client

import dev.inmo.postssystem.client.fsm.ui.*
import dev.inmo.postssystem.client.ui.*
import dev.inmo.postssystem.client.ui.fsm.*
import dev.inmo.postssystem.client.ui.fsm.UIFSMStateSerializer
import dev.inmo.postssystem.features.auth.client.ui.AuthUIModel
import dev.inmo.postssystem.features.auth.client.ui.AuthUIViewModel
import dev.inmo.postssystem.features.auth.common.AuthTokenInfo
import dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler
import dev.inmo.micro_utils.fsm.common.CheckableHandlerHolder
import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
import dev.inmo.postssystem.client.settings.auth.AuthSettings
import kotlinx.browser.document
import kotlinx.browser.localStorage
import kotlinx.coroutines.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.serializer
import org.koin.core.Koin
import org.koin.core.context.loadKoinModules
import org.koin.core.parameter.ParametersHolder
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.module
import org.w3c.dom.HTMLElement
import kotlin.reflect.KClass

val defaultTypedSerializer = TypedSerializer<Any>(
    "AuthTokenInfo" to AuthTokenInfo.serializer(),
    "String" to String.serializer(),
    "Int" to Int.serializer(),
    "Long" to Long.serializer(),
    "Short" to Short.serializer(),
    "Byte" to Byte.serializer(),
    "Float" to Float.serializer(),
    "Double" to Double.serializer(),
    "UIFSMState" to UIFSMStateSerializer
)

fun baseKoin(): Koin {
    val anyToString: suspend Any.() -> String = {
        defaultSerialFormat.encodeToString(
            defaultTypedSerializer,
            this
        )
    }
    return baseKoin(
        CoroutineScope(
            Dispatchers.Default +
                ContextSafelyExceptionHandler { it.printStackTrace() } +
                CoroutineExceptionHandler { _, it -> it.printStackTrace() }
        ),
        {
            CookiesKeyValueRepo.withMapper<String, Any, String, String>(
                { this },
                {
                    runCatching {
                        anyToString()
                    }.getOrElse {
                        if (it is NoSuchElementException) {
                            val name = this::class.simpleName!!
                            defaultTypedSerializer.include(name, serializer())
                            anyToString()
                        } else {
                            throw it
                        }
                    }
                },
                { this },
                {
                    defaultSerialFormat.decodeFromString(
                        defaultTypedSerializer,
                        this
                    )
                }
            )
        },
        {
            OneStateUIFSMStatesRepo(get(), localStorage)
        }
    ) {
        first.apply {
            second.apply {
                loadKoinModules(
                    module {
                        factory { document.getElementById("main") as HTMLElement }

                        factory<AuthUIModel> { DefaultAuthUIModel(get(), get()) }
                        factory { AuthUIViewModel(get()) }
                        factory { AuthView(get(), get(UIScopeQualifier))  }
                    }
                )
                strictlyOn<AuthUIFSMState>(get<AuthView>())

                // Костыль, в JS на момент Пн дек  6 14:19:29 +06 2021 если использовать strictlyOn генерируются
                // некорректные безымянные классы (у них отсутствует метод handleState)
                class DefaultStateHandlerWrapper<T : UIFSMState>(
                    private val klass: KClass<out UIFSMHandler<T>>,
                    private val stateKlass: KClass<T>,
                    private val qualifier: Qualifier? = null,
                    private val parameters: ((T) -> ParametersHolder)? = null
                ) : CheckableHandlerHolder<UIFSMState, UIFSMState> {
                    override suspend fun StatesMachine<in UIFSMState>.handleState(state: UIFSMState): UIFSMState? {
                        @Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
                        val state = state as T
                        return runCatching {
                            val authSettings = get<AuthSettings>()
                            authSettings.loadingJob.join()
                            if (authSettings.authorizedDIModule.value == null) {
                                error("Can't perform state $state: Auth module was not initialized")
                            } else {
                                get<UIFSMHandler<T>>(klass, qualifier, parameters ?.let { { it(state) } }).run {
                                    handleState(state)
                                }
                            }
                        }.getOrElse { e ->
                            e.printStackTrace()
                            AuthUIFSMState(state)
                        }
                    }

                    override suspend fun checkHandleable(state: UIFSMState): Boolean = stateKlass.isInstance(state)
                }
                inline fun <reified T : UIFSMState> registerHandler(
                    klass: KClass<out UIFSMHandler<T>>,
                    qualifier: Qualifier? = null,
                    parameters: ((T) -> ParametersHolder)? = null
                ) = add(
                    DefaultStateHandlerWrapper<T>(
                        klass,
                        T::class,
                        qualifier,
                        parameters
                    )
                )

            }
        }
    }
}
