package de.krisenvorrat.server

import de.krisenvorrat.server.db.DatabaseFactory
import de.krisenvorrat.server.model.ErrorResponse
import de.krisenvorrat.server.model.LoginRequest
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.config.*
import io.ktor.server.testing.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

class RateLimitingTest {

    private val json = Json { ignoreUnknownKeys = true }

    private fun testApp(block: suspend ApplicationTestBuilder.() -> Unit) = testApplication {
        environment {
            config = MapApplicationConfig(*testMapConfig().toTypedArray())
        }
        application {
            DatabaseFactory.init(
                jdbcUrl = "jdbc:h2:mem:ratelimit_test_${System.nanoTime()};DB_CLOSE_DELAY=-1",
                driver = "org.h2.Driver",
                adminPassword = "test-admin-pw"
            )
            configurePlugins()
        }
        block()
    }

    @Test
    fun test_authLogin_exceedsRateLimit_returns429() = testApp {
        // Given: auth rate limit is 10 req/min
        val loginBody = json.encodeToString(LoginRequest("admin", "wrong-password"))

        // When: send 11 requests (exceeding the 10 req/min limit)
        var lastStatus: HttpStatusCode = HttpStatusCode.OK
        for (i in 1..11) {
            val response = client.post("/api/auth/login") {
                contentType(ContentType.Application.Json)
                setBody(loginBody)
            }
            lastStatus = response.status
        }

        // Then: the 11th request should be rate-limited
        assertEquals(HttpStatusCode.TooManyRequests, lastStatus)
    }

    @Test
    fun test_authLogin_withinRateLimit_notBlocked() = testApp {
        // Given: auth rate limit is 10 req/min
        val loginBody = json.encodeToString(LoginRequest("admin", "wrong-password"))

        // When: send exactly 10 requests (within limit)
        val statuses = (1..10).map {
            client.post("/api/auth/login") {
                contentType(ContentType.Application.Json)
                setBody(loginBody)
            }.status
        }

        // Then: none should be 429
        assertTrue(
            "Expected no 429 responses within limit, got: $statuses",
            statuses.none { it == HttpStatusCode.TooManyRequests }
        )
    }

    @Test
    fun test_inventoryGet_exceedsRateLimit_returns429() = testApp {
        // Given: inventory rate limit is 60 req/min
        val token = createTestAccessToken()

        // When: send 61 requests
        var lastStatus: HttpStatusCode = HttpStatusCode.OK
        for (i in 1..61) {
            val response = client.get("/api/inventory") {
                bearerAuth(token)
            }
            lastStatus = response.status
        }

        // Then: the 61st request should be rate-limited
        assertEquals(HttpStatusCode.TooManyRequests, lastStatus)
    }

    @Test
    fun test_healthEndpoint_noRateLimit_alwaysOk() = testApp {
        // Given: health endpoint has no rate limit

        // When: send many requests
        val statuses = (1..100).map {
            client.get("/api/health").status
        }

        // Then: all should be 200
        assertTrue(
            "Health endpoint should never be rate-limited",
            statuses.all { it == HttpStatusCode.OK }
        )
    }

    @Test
    fun test_rateLimitResponse_containsRetryAfterHeader() = testApp {
        // Given: exceed auth rate limit
        val loginBody = json.encodeToString(LoginRequest("admin", "wrong-password"))
        repeat(10) {
            client.post("/api/auth/login") {
                contentType(ContentType.Application.Json)
                setBody(loginBody)
            }
        }

        // When: send one more request
        val response = client.post("/api/auth/login") {
            contentType(ContentType.Application.Json)
            setBody(loginBody)
        }

        // Then: should have Retry-After header
        assertEquals(HttpStatusCode.TooManyRequests, response.status)
        val retryAfter = response.headers["Retry-After"]
        assertTrue(
            "Expected Retry-After header in 429 response",
            retryAfter != null
        )
    }
}
