package dev.inmo.postssystem.features.auth.server

import dev.inmo.postssystem.features.auth.common.*
import dev.inmo.postssystem.features.auth.server.tokens.AuthTokensService
import dev.inmo.postssystem.features.common.server.sessions.ApplicationAuthenticationConfigurator
import dev.inmo.postssystem.features.users.common.User
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.ktor.server.configurators.*
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.HttpStatusCode
import io.ktor.response.respond
import io.ktor.routing.*
import io.ktor.sessions.*
import kotlinx.serialization.builtins.nullable

data class AuthUserPrincipal(
    val user: User
) : Principal

fun User.principal() = AuthUserPrincipal(this)


class AuthenticationRoutingConfigurator(
    private val authFeature: AuthFeature,
    private val authTokensService: AuthTokensService,
    private val unifiedRouter: UnifiedRouter
) : ApplicationRoutingConfigurator.Element, ApplicationAuthenticationConfigurator.Element {
    override fun Route.invoke() {
        unifiedRouter.apply {
            route(authRootPathPart) {
                post(authAuthPathPart) {
                    safely(
                        {
                            // TODO:: add error info
                            it.printStackTrace()
                            call.respond(
                                HttpStatusCode.InternalServerError,
                                "Something went wrong"
                            )
                        }
                    ) {
                        val creds = uniload(AuthCreds.serializer())

                        val tokenInfo = authFeature.auth(creds)

                        if (tokenInfo == null) {
                            if (call.response.status() == null) {
                                call.respond(HttpStatusCode.Forbidden)
                            }
                        } else {
                            call.sessions.set(tokenSessionKey, tokenInfo.token)
                            unianswer(
                                AuthTokenInfo.serializer().nullable,
                                tokenInfo
                            )
                        }
                    }
                }
                post(authRefreshPathPart) {
                    safely(
                        {
                            // TODO:: add error info
                            call.respond(
                                HttpStatusCode.InternalServerError,
                                "Something went wrong"
                            )
                        }
                    ) {
                        val refreshToken = uniload(RefreshToken.serializer())

                        val tokenInfo = authFeature.refresh(refreshToken)

                        if (tokenInfo == null) {
                            if (call.response.status() == null) {
                                call.respond(HttpStatusCode.Forbidden)
                            }
                        } else {
                            call.sessions.set(tokenSessionKey, tokenInfo.token)
                            unianswer(
                                AuthTokenInfo.serializer().nullable,
                                tokenInfo
                            )
                        }
                    }
                }
                post(authGetMePathPart) {
                    safely(
                        {
                            // TODO:: add error info
                            call.respond(
                                HttpStatusCode.InternalServerError,
                                "Something went wrong"
                            )
                        }
                    ) {
                        unianswer(
                            User.serializer().nullable,
                            authFeature.getMe(
                                uniload(AuthToken.serializer())
                            )
                        )
                    }
                }
            }
        }
    }

    override fun Authentication.Configuration.invoke() {
        session<AuthToken> {
            validate {
                val result = authTokensService.getUserPrincipal(it)
                if (result.isSuccess) {
                    result.getOrThrow().principal()
                } else {
                    null
                }
            }
            challenge { call.respond(HttpStatusCode.Unauthorized) }
        }
    }
}
