package de.krisenvorrat.server

import de.krisenvorrat.server.db.DatabaseFactory
import de.krisenvorrat.shared.model.CategoryDto
import de.krisenvorrat.shared.model.InventoryDto
import de.krisenvorrat.shared.model.ItemDto
import de.krisenvorrat.shared.model.LocationDto
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 org.junit.Assert.assertEquals
import org.junit.Test

class PatchItemTest {

    private val json = Json { ignoreUnknownKeys = true }
    private val token = createTestAccessToken()

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

    private fun seedInventory(): String {
        val item = ItemDto(
            id = "item-patch-1",
            name = "Dosenbrot",
            categoryId = 1,
            quantity = 5.0,
            unit = "Stück",
            unitPrice = 3.99,
            kcalPerKg = 250,
            expiryDate = "2027-06-15",
            locationId = 1,
            notes = "Vollkornbrot",
            lastUpdated = 1715000000L
        )
        val inventory = InventoryDto(
            categories = listOf(CategoryDto(id = 1, name = "Konserven")),
            locations = listOf(LocationDto(id = 1, name = "Keller")),
            items = listOf(item),
            settings = emptyList()
        )
        return json.encodeToString(InventoryDto.serializer(), inventory)
    }

    // ── Happy Path ───────────────────────────────────────────────────────────

    @Test
    fun test_patchItem_partialUpdate_onlyUpdatesProvidedFields() = testApp {
        // Given
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(seedInventory())
        }

        // When – only update name and quantity
        val response = client.patch("/api/inventory/items/item-patch-1") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody("""{"name":"Pumpernickel","quantity":10.0}""")
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val result = json.decodeFromString<ItemDto>(response.bodyAsText())
        assertEquals("Pumpernickel", result.name)
        assertEquals(10.0, result.quantity, 0.001)
        // Unchanged fields remain
        assertEquals("Stück", result.unit)
        assertEquals(3.99, result.unitPrice, 0.001)
        assertEquals(250, result.kcalPerKg)
        assertEquals("2027-06-15", result.expiryDate)
        assertEquals(1, result.locationId)
        assertEquals("Vollkornbrot", result.notes)
    }

    @Test
    fun test_patchItem_updateSingleField_returnsFullItem() = testApp {
        // Given
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(seedInventory())
        }

        // When – only update kcalPerKg
        val response = client.patch("/api/inventory/items/item-patch-1") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody("""{"kcalPerKg":3200}""")
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val result = json.decodeFromString<ItemDto>(response.bodyAsText())
        assertEquals(3200, result.kcalPerKg)
        assertEquals("Dosenbrot", result.name)
        assertEquals("item-patch-1", result.id)
    }

    @Test
    fun test_patchItem_updateNullableFieldToNull_setsNull() = testApp {
        // Given
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(seedInventory())
        }

        // When – set expiryDate to null
        val response = client.patch("/api/inventory/items/item-patch-1") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody("""{"expiryDate":null}""")
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val result = json.decodeFromString<ItemDto>(response.bodyAsText())
        assertEquals(null, result.expiryDate)
    }

    @Test
    fun test_patchItem_persistsChanges() = testApp {
        // Given
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(seedInventory())
        }

        // When – patch then reload full inventory
        client.patch("/api/inventory/items/item-patch-1") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody("""{"name":"Geänderter Name"}""")
        }

        val getResponse = client.get("/api/inventory") {
            bearerAuth(token)
        }

        // Then
        val inventory = json.decodeFromString<InventoryDto>(getResponse.bodyAsText())
        assertEquals("Geänderter Name", inventory.items[0].name)
    }

    // ── 404: Item not found ──────────────────────────────────────────────────

    @Test
    fun test_patchItem_nonExistentItem_returns404() = testApp {
        // Given – empty inventory
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), InventoryDto(
                categories = emptyList(), locations = emptyList(), items = emptyList(), settings = emptyList()
            )))
        }

        // When
        val response = client.patch("/api/inventory/items/nonexistent-id") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody("""{"name":"Test"}""")
        }

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

    // ── Authentication ───────────────────────────────────────────────────────

    @Test
    fun test_patchItem_noToken_returns401() = testApp {
        // When – no bearer token
        val response = client.patch("/api/inventory/items/item-patch-1") {
            contentType(ContentType.Application.Json)
            setBody("""{"name":"Test"}""")
        }

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

    @Test
    fun test_patchItem_invalidToken_returns401() = testApp {
        // When – invalid token
        val response = client.patch("/api/inventory/items/item-patch-1") {
            bearerAuth("invalid-token-value")
            contentType(ContentType.Application.Json)
            setBody("""{"name":"Test"}""")
        }

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

    // ── Validation ───────────────────────────────────────────────────────────

    @Test
    fun test_patchItem_itemIdTooLong_returns400() = testApp {
        // When
        val longId = "X".repeat(37)
        val response = client.patch("/api/inventory/items/$longId") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody("""{"name":"Test"}""")
        }

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

    @Test
    fun test_patchItem_unitTooLong_returns400() = testApp {
        // Given
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(seedInventory())
        }

        // When
        val response = client.patch("/api/inventory/items/item-patch-1") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody("""{"unit":"${"X".repeat(51)}"}""")
        }

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

    @Test
    fun test_patchItem_emptyBody_updatesNothing() = testApp {
        // Given
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(seedInventory())
        }

        // When – empty JSON object
        val response = client.patch("/api/inventory/items/item-patch-1") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody("""{}""")
        }

        // Then – item unchanged
        assertEquals(HttpStatusCode.OK, response.status)
        val result = json.decodeFromString<ItemDto>(response.bodyAsText())
        assertEquals("Dosenbrot", result.name)
        assertEquals(5.0, result.quantity, 0.001)
    }
}
