package de.krisenvorrat.server

import de.krisenvorrat.server.db.DatabaseFactory
import de.krisenvorrat.server.db.Users
import de.krisenvorrat.server.model.ErrorResponse
import de.krisenvorrat.server.model.LoginRequest
import de.krisenvorrat.server.model.LoginResponse
import de.krisenvorrat.server.model.RefreshRequest
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.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.mindrot.jbcrypt.BCrypt

class AuthenticationTest {

    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:auth_test_${System.nanoTime()};DB_CLOSE_DELAY=-1",
                driver = "org.h2.Driver",
                adminPassword = "test-admin-pw"
            )
            configurePlugins()
        }
        block()
    }

    @Test
    fun test_inventoryGet_validJwtToken_returns200() = testApp {
        val token = createTestAccessToken()
        val response = client.get("/api/inventory") { bearerAuth(token) }
        assertEquals(HttpStatusCode.OK, response.status)
    }

    @Test
    fun test_inventoryGet_missingToken_returns401() = testApp {
        val response = client.get("/api/inventory")
        assertEquals(HttpStatusCode.Unauthorized, response.status)
        val error = json.decodeFromString<ErrorResponse>(response.bodyAsText())
        assertEquals(401, error.status)
    }

    @Test
    fun test_inventoryGet_invalidToken_returns401() = testApp {
        val response = client.get("/api/inventory") { bearerAuth("not-a-valid-jwt") }
        assertEquals(HttpStatusCode.Unauthorized, response.status)
    }

    @Test
    fun test_inventoryPut_missingToken_returns401() = testApp {
        val response = client.put("/api/inventory") {
            contentType(ContentType.Application.Json)
            setBody("{}")
        }
        assertEquals(HttpStatusCode.Unauthorized, response.status)
    }

    @Test
    fun test_healthEndpoint_noToken_returns200() = testApp {
        val response = client.get("/api/health")
        assertEquals(HttpStatusCode.OK, response.status)
        assertEquals("OK", response.bodyAsText())
    }

    @Test
    fun test_login_validCredentials_returnsTokens() = testApp {
        val loginBody = json.encodeToString(LoginRequest("admin", "test-admin-pw"))
        val response = client.post("/api/auth/login") {
            contentType(ContentType.Application.Json)
            setBody(loginBody)
        }
        assertEquals(HttpStatusCode.OK, response.status)
        val body = json.decodeFromString<LoginResponse>(response.bodyAsText())
        assertFalse(body.accessToken.isBlank())
        assertFalse(body.refreshToken.isBlank())
    }

    @Test
    fun test_login_wrongPassword_returns401() = testApp {
        val loginBody = json.encodeToString(LoginRequest("admin", "wrongpassword"))
        val response = client.post("/api/auth/login") {
            contentType(ContentType.Application.Json)
            setBody(loginBody)
        }
        assertEquals(HttpStatusCode.Unauthorized, response.status)
    }

    @Test
    fun test_login_unknownUser_returns401() = testApp {
        val loginBody = json.encodeToString(LoginRequest("nobody", "password"))
        val response = client.post("/api/auth/login") {
            contentType(ContentType.Application.Json)
            setBody(loginBody)
        }
        assertEquals(HttpStatusCode.Unauthorized, response.status)
    }

    @Test
    fun test_refresh_validRefreshToken_returnsNewTokens() = testApp {
        // First login to get a refresh token
        val loginBody = json.encodeToString(LoginRequest("admin", "test-admin-pw"))
        val loginResponse = client.post("/api/auth/login") {
            contentType(ContentType.Application.Json)
            setBody(loginBody)
        }
        val loginData = json.decodeFromString<LoginResponse>(loginResponse.bodyAsText())

        // Use refresh token
        val refreshBody = json.encodeToString(RefreshRequest(loginData.refreshToken))
        val refreshResponse = client.post("/api/auth/refresh") {
            contentType(ContentType.Application.Json)
            setBody(refreshBody)
        }
        assertEquals(HttpStatusCode.OK, refreshResponse.status)
        val refreshData = json.decodeFromString<LoginResponse>(refreshResponse.bodyAsText())
        assertFalse(refreshData.accessToken.isBlank())
    }

    @Test
    fun test_refresh_invalidToken_returns401() = testApp {
        val refreshBody = json.encodeToString(RefreshRequest("invalid-token"))
        val response = client.post("/api/auth/refresh") {
            contentType(ContentType.Application.Json)
            setBody(refreshBody)
        }
        assertEquals(HttpStatusCode.Unauthorized, response.status)
    }
}
