package de.krisenvorrat.server

import de.krisenvorrat.server.db.DatabaseFactory
import de.krisenvorrat.server.model.ErrorResponse
import de.krisenvorrat.shared.model.MessageDto
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.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test

class MessageApiTest {

    private val json = Json {
        ignoreUnknownKeys = true
        encodeDefaults = true
    }

    private val adminToken = createTestAccessToken(
        userId = TEST_ADMIN_ID,
        username = TEST_ADMIN_USERNAME,
        isAdmin = true
    )

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

    private suspend fun ApplicationTestBuilder.createUser(username: String, password: String = "pass123"): String {
        client.post("/api/admin/users") {
            bearerAuth(adminToken)
            contentType(ContentType.Application.Json)
            setBody("""{"username":"$username","password":"$password"}""")
        }
        val listResponse = client.get("/api/admin/users") { bearerAuth(adminToken) }
        val users = json.decodeFromString<JsonArray>(listResponse.bodyAsText())
        return users.first { it.jsonObject["username"]?.jsonPrimitive?.content == username }
            .jsonObject["id"]!!.jsonPrimitive.content
    }

    // ── Send Message ─────────────────────────────────────────────────────────

    @Test
    fun test_sendMessage_validRequest_returns201() = testApp {
        // Given
        val bobId = createUser("bob")
        val aliceId = createUser("alice")
        val aliceToken = createTestAccessToken(userId = aliceId, username = "alice")

        // When
        val response = client.post("/api/messages") {
            bearerAuth(aliceToken)
            contentType(ContentType.Application.Json)
            setBody("""{"receiverId":"$bobId","body":"Hallo Bob!","sentAt":1700000000000}""")
        }

        // Then
        assertEquals(HttpStatusCode.Created, response.status)
        val msg = json.decodeFromString<MessageDto>(response.bodyAsText())
        assertEquals("Hallo Bob!", msg.body)
        assertEquals(aliceId, msg.senderId)
        assertEquals(bobId, msg.receiverId)
        assertNotNull(msg.id)
    }

    @Test
    fun test_sendMessage_blankBody_returns400() = testApp {
        // Given
        val bobId = createUser("bob")
        val aliceId = createUser("alice")
        val aliceToken = createTestAccessToken(userId = aliceId, username = "alice")

        // When
        val response = client.post("/api/messages") {
            bearerAuth(aliceToken)
            contentType(ContentType.Application.Json)
            setBody("""{"receiverId":"$bobId","body":"  ","sentAt":1700000000000}""")
        }

        // Then
        assertEquals(HttpStatusCode.BadRequest, response.status)
    }

    @Test
    fun test_sendMessage_receiverNotFound_returns404() = testApp {
        // Given
        val aliceId = createUser("alice")
        val aliceToken = createTestAccessToken(userId = aliceId, username = "alice")

        // When
        val response = client.post("/api/messages") {
            bearerAuth(aliceToken)
            contentType(ContentType.Application.Json)
            setBody("""{"receiverId":"non-existent-id","body":"Hello","sentAt":1700000000000}""")
        }

        // Then
        assertEquals(HttpStatusCode.NotFound, response.status)
        val error = json.decodeFromString<ErrorResponse>(response.bodyAsText())
        assertEquals(404, error.status)
    }

    @Test
    fun test_sendMessage_noAuth_returns401() = testApp {
        // When
        val response = client.post("/api/messages") {
            contentType(ContentType.Application.Json)
            setBody("""{"receiverId":"some-id","body":"Hello","sentAt":1700000000000}""")
        }

        // Then
        assertEquals(HttpStatusCode.Unauthorized, response.status)
    }

    // ── Get Conversation ─────────────────────────────────────────────────────

    @Test
    fun test_getConversation_returnsMessages() = testApp {
        // Given
        val bobId = createUser("bob")
        val aliceId = createUser("alice")
        val aliceToken = createTestAccessToken(userId = aliceId, username = "alice")
        val bobToken = createTestAccessToken(userId = bobId, username = "bob")

        // Send a message from Alice to Bob
        client.post("/api/messages") {
            bearerAuth(aliceToken)
            contentType(ContentType.Application.Json)
            setBody("""{"receiverId":"$bobId","body":"Hi Bob!","sentAt":1700000001000}""")
        }

        // Send a reply from Bob to Alice
        client.post("/api/messages") {
            bearerAuth(bobToken)
            contentType(ContentType.Application.Json)
            setBody("""{"receiverId":"$aliceId","body":"Hi Alice!","sentAt":1700000002000}""")
        }

        // When – Alice gets conversation with Bob
        val response = client.get("/api/messages/$bobId") {
            bearerAuth(aliceToken)
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val messages = json.decodeFromString<List<MessageDto>>(response.bodyAsText())
        assertEquals(2, messages.size)
    }

    @Test
    fun test_getConversation_emptyConversation_returnsEmptyList() = testApp {
        // Given
        val bobId = createUser("bob")
        val aliceId = createUser("alice")
        val aliceToken = createTestAccessToken(userId = aliceId, username = "alice")

        // When
        val response = client.get("/api/messages/$bobId") {
            bearerAuth(aliceToken)
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val messages = json.decodeFromString<List<MessageDto>>(response.bodyAsText())
        assertTrue(messages.isEmpty())
    }

    @Test
    fun test_getConversation_noAuth_returns401() = testApp {
        // When
        val response = client.get("/api/messages/some-user-id")

        // Then
        assertEquals(HttpStatusCode.Unauthorized, response.status)
    }

    // ── Message with custom ID ───────────────────────────────────────────────

    @Test
    fun test_sendMessage_withCustomId_usesProvidedId() = testApp {
        // Given
        val bobId = createUser("bob")
        val aliceId = createUser("alice")
        val aliceToken = createTestAccessToken(userId = aliceId, username = "alice")
        val customId = "custom-msg-id-001"

        // When
        val response = client.post("/api/messages") {
            bearerAuth(aliceToken)
            contentType(ContentType.Application.Json)
            setBody("""{"id":"$customId","receiverId":"$bobId","body":"Test","sentAt":1700000000000}""")
        }

        // Then
        assertEquals(HttpStatusCode.Created, response.status)
        val msg = json.decodeFromString<MessageDto>(response.bodyAsText())
        assertEquals(customId, msg.id)
    }

    // ── Response format ──────────────────────────────────────────────────────

    @Test
    fun test_sendMessage_responseContainsAllFields() = testApp {
        // Given
        val bobId = createUser("bob")
        val aliceId = createUser("alice")
        val aliceToken = createTestAccessToken(userId = aliceId, username = "alice")

        // When
        val response = client.post("/api/messages") {
            bearerAuth(aliceToken)
            contentType(ContentType.Application.Json)
            setBody("""{"receiverId":"$bobId","body":"Test message","sentAt":1700000000000}""")
        }

        // Then
        assertEquals(HttpStatusCode.Created, response.status)
        val msg = json.decodeFromString<MessageDto>(response.bodyAsText())
        assertFalse(msg.id.isBlank())
        assertEquals(aliceId, msg.senderId)
        assertEquals("alice", msg.senderUsername)
        assertEquals(bobId, msg.receiverId)
        assertEquals("Test message", msg.body)
        assertEquals(1700000000000L, msg.sentAt)
    }
}
