package dev.ulfrx.recipe.auth

import dev.lokksmith.Lokksmith
import dev.lokksmith.client.Client
import dev.lokksmith.client.request.flow.AuthFlow
import dev.lokksmith.client.request.flow.AuthFlowResultProvider
import dev.lokksmith.client.request.flow.authorizationCode.AuthorizationCodeFlow
import dev.lokksmith.client.request.flow.endSession.EndSessionFlow
import dev.lokksmith.client.request.parameter.Scope
import dev.lokksmith.discoveryUrl
import dev.lokksmith.id
import dev.ulfrx.recipe.shared.Constants

internal const val LOKKSMITH_CLIENT_KEY = "recipe-app"
internal const val LOKKSMITH_AUTH_STATE_MARKER = "lokksmith:$LOKKSMITH_CLIENT_KEY"

internal suspend fun Lokksmith.recipeClient(): Client =
    getOrCreate(LOKKSMITH_CLIENT_KEY) {
        id = Constants.OIDC_CLIENT_ID
        discoveryUrl = Constants.OIDC_ISSUER + ".well-known/openid-configuration"
    }

internal fun Client.recipeAuthorizationCodeFlow(): AuthFlow =
    authorizationCodeFlow(
        AuthorizationCodeFlow.Request(
            redirectUri = Constants.OIDC_REDIRECT_URI,
            scope = setOf(Scope.Profile, Scope.Email, Scope.Custom("offline_access")),
        ),
    )

internal fun Client.recipeEndSessionFlow(): AuthFlow? = endSessionFlow(EndSessionFlow.Request(redirectUri = Constants.OIDC_REDIRECT_URI))

internal suspend fun Client.toOidcSuccess(): OidcResult.Success {
    var freshTokens: Client.Tokens? = null
    runWithTokens { tokens -> freshTokens = tokens }
    val tokens = checkNotNull(freshTokens) { "Lokksmith returned no tokens" }
    return OidcResult.Success(
        authStateJson = LOKKSMITH_AUTH_STATE_MARKER,
        accessToken = tokens.accessToken.token,
        idToken = tokens.idToken.raw,
        expiresAtEpochMillis = tokens.accessToken.expiresAt?.let { it * 1_000L } ?: 0L,
    )
}

internal fun AuthFlowResultProvider.Result.toOidcFailureOrNull(): OidcResult? =
    when (this) {
        is AuthFlowResultProvider.Result.Success -> null

        is AuthFlowResultProvider.Result.Cancelled -> OidcResult.Cancelled

        is AuthFlowResultProvider.Result.Error -> OidcResult.AuthError(message ?: "OIDC flow failed")

        AuthFlowResultProvider.Result.Undefined,
        is AuthFlowResultProvider.Result.Processing,
        -> OidcResult.AuthError("OIDC flow did not complete")
    }
