package dev.ulfrx.recipe.auth

import com.auth0.jwk.JwkProvider
import com.auth0.jwk.JwkProviderBuilder
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.auth.Authentication
import io.ktor.server.auth.jwt.JWTPrincipal
import io.ktor.server.auth.jwt.jwt
import java.net.URI
import java.util.concurrent.TimeUnit

/**
 * Installs the `jwt("authentik")` Ktor authentication provider per D-21..D-23.
 *
 * Production callers use the no-arg form, which reads [AuthConfig] from
 * `application.conf` and builds a cached, rate-limited [JwkProvider] from the
 * issuer's JWKS endpoint (D-22: `cached(10, 15, MINUTES)` + `rateLimited(10, 1, MINUTES)`).
 *
 * Tests override [jwkProvider] with [JwtTestSupport.provider] to keep verification
 * in-process — `testApplication` has no usable network port, so the URL-based
 * builder isn't viable.
 *
 * Validate block enforces D-21's non-empty `sub` rule. Returning `null` from
 * `validate` produces 401 — Ktor's default challenge handler is sufficient; we
 * deliberately avoid logging Authorization headers or token bodies (D-23).
 */
public fun Application.configureAuthentication(
    authConfig: AuthConfig = AuthConfig.fromApplicationConfig(environment.config),
    jwkProvider: JwkProvider =
        JwkProviderBuilder(URI(authConfig.jwksUrl).toURL())
            .cached(10, 15, TimeUnit.MINUTES)
            .rateLimited(10, 1, TimeUnit.MINUTES)
            .build(),
) {
    install(Authentication) {
        jwt("authentik") {
            realm = "recipe"
            verifier(jwkProvider, authConfig.issuer) {
                withIssuer(authConfig.issuer)
                withAudience(authConfig.audience)
                // D-21: clock-skew tolerance pinned at 30 seconds (acceptLeeway(30 seconds)).
                // Honor the AuthConfig override if set lower for tests, but never exceed 30.
                acceptLeeway(authConfig.leewaySeconds.coerceAtMost(30L))
            }
            validate { credential ->
                val sub = credential.payload.subject
                if (sub.isNullOrBlank()) null else JWTPrincipal(credential.payload)
            }
        }
    }
}
