package dev.ulfrx.recipe.auth

import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.auth.authenticate
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import io.ktor.server.testing.testApplication
import kotlin.test.Test
import kotlin.test.assertEquals

/**
 * Negative + positive JWT validation coverage for the `/api/v1/me` boundary
 * (AUTH-03, D-21..D-23). Tests run against a hand-mounted protected route under
 * `authenticate("authentik")` so they exercise the same Ktor verifier path as
 * production without depending on Postgres or Flyway (those land in
 * `MeRouteTest` per Plan 02-02 Task 3).
 */
class AuthJwtTest {
    private fun authConfigFor(support: JwtTestSupport): AuthConfig =
        AuthConfig(
            issuer = support.issuer,
            audience = support.audience,
            // jwksUrl is unused when a JwkProvider override is supplied — the value
            // just needs to be a syntactically-valid URL so AuthConfig invariants hold.
            jwksUrl = "https://test-authentik.invalid/application/o/jwks/",
            leewaySeconds = 30,
        )

    private fun Application.installProtectedRoute(support: JwtTestSupport) {
        install(ContentNegotiation) { json() }
        configureAuthentication(authConfigFor(support), support.provider)
        routing {
            authenticate("authentik") {
                get("/protected") { call.respondText("ok") }
            }
        }
    }

    @Test
    fun `no Authorization header returns 401`() =
        testApplication {
            val support = JwtTestSupport()
            application { installProtectedRoute(support) }
            val response = client.get("/protected")
            assertEquals(HttpStatusCode.Unauthorized, response.status)
        }

    @Test
    fun `expired token returns 401`() =
        testApplication {
            val support = JwtTestSupport()
            application { installProtectedRoute(support) }
            val token = support.mint(expiresInSeconds = -60)
            val response =
                client.get("/protected") { header(HttpHeaders.Authorization, "Bearer $token") }
            assertEquals(HttpStatusCode.Unauthorized, response.status)
        }

    @Test
    fun `wrong issuer returns 401`() =
        testApplication {
            val support = JwtTestSupport()
            application { installProtectedRoute(support) }
            val token = support.mint(iss = "https://attacker.invalid/")
            val response =
                client.get("/protected") { header(HttpHeaders.Authorization, "Bearer $token") }
            assertEquals(HttpStatusCode.Unauthorized, response.status)
        }

    @Test
    fun `wrong audience returns 401`() =
        testApplication {
            val support = JwtTestSupport()
            application { installProtectedRoute(support) }
            val token = support.mint(aud = "some-other-app")
            val response =
                client.get("/protected") { header(HttpHeaders.Authorization, "Bearer $token") }
            assertEquals(HttpStatusCode.Unauthorized, response.status)
        }

    @Test
    fun `blank sub returns 401`() =
        testApplication {
            val support = JwtTestSupport()
            application { installProtectedRoute(support) }
            // JWT spec disallows an empty `sub` claim (auth0/java-jwt rejects ""),
            // so we omit the claim entirely — the validate block must reject both
            // null and blank as required by D-21.
            val token = support.mint(sub = null)
            val response =
                client.get("/protected") { header(HttpHeaders.Authorization, "Bearer $token") }
            assertEquals(HttpStatusCode.Unauthorized, response.status)
        }

    @Test
    fun `valid RS256 token returns 200 from a protected test route`() =
        testApplication {
            val support = JwtTestSupport()
            application { installProtectedRoute(support) }
            val token = support.mint()
            val response =
                client.get("/protected") { header(HttpHeaders.Authorization, "Bearer $token") }
            assertEquals(HttpStatusCode.OK, response.status)
        }
}
