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.Assert.assertTrue
import org.junit.Test

class UserIsolationTest {

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

    private val tokenA = createTestAccessToken(userId = "isolation-user-a", username = "userA")
    private val tokenB = createTestAccessToken(userId = "isolation-user-b", username = "userB")

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

    @Test
    fun test_userA_cannotSeeUserB_inventory() = testApp {
        // Given: User A pusht ein Inventar
        val inventoryA = InventoryDto(
            categories = listOf(CategoryDto(id = 1, name = "User-A-Kategorie")),
            locations = listOf(LocationDto(id = 1, name = "User-A-Lager")),
            items = listOf(
                ItemDto(
                    id = "item-a1",
                    name = "User-A-Artikel",
                    categoryId = 1,
                    quantity = 5.0,
                    unit = "Stück",
                    unitPrice = 2.99,
                    kcalPerKg = null,
                    expiryDate = null,
                    locationId = 1,
                    notes = "",
                    lastUpdated = 1715000000L
                )
            ),
            settings = emptyList()
        )
        client.put("/api/inventory") {
            bearerAuth(tokenA)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), inventoryA))
        }

        // When: User B ruft GET ab (ohne eigene Daten)
        val response = client.get("/api/inventory") { bearerAuth(tokenB) }

        // Then: User B sieht leeres Inventar – nicht User A's Daten
        assertEquals(HttpStatusCode.OK, response.status)
        val inventoryB = json.decodeFromString<InventoryDto>(response.bodyAsText())
        assertTrue("User B must not see User A's items", inventoryB.items.isEmpty())
        assertTrue("User B must not see User A's categories", inventoryB.categories.isEmpty())
        assertTrue("User B must not see User A's locations", inventoryB.locations.isEmpty())
    }

    @Test
    fun test_userA_and_userB_have_separate_inventories() = testApp {
        // Given: User A und User B haben jeweils eigene Inventare
        val inventoryA = InventoryDto(
            categories = listOf(CategoryDto(id = 1, name = "Konserven-A")),
            locations = listOf(LocationDto(id = 1, name = "Keller-A")),
            items = listOf(
                ItemDto(
                    id = "item-a1", name = "Dosenbrot", categoryId = 1,
                    quantity = 5.0, unit = "Stück", unitPrice = 3.99,
                    kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 100L
                )
            ),
            settings = emptyList()
        )
        val inventoryB = InventoryDto(
            categories = listOf(CategoryDto(id = 1, name = "Hygiene-B")),
            locations = listOf(LocationDto(id = 1, name = "Bad-B")),
            items = listOf(
                ItemDto(
                    id = "item-b1", name = "Seife", categoryId = 1,
                    quantity = 3.0, unit = "Stück", unitPrice = 1.50,
                    kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = 200L
                )
            ),
            settings = emptyList()
        )

        client.put("/api/inventory") {
            bearerAuth(tokenA)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), inventoryA))
        }
        client.put("/api/inventory") {
            bearerAuth(tokenB)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), inventoryB))
        }

        // When
        val resultA = json.decodeFromString<InventoryDto>(
            client.get("/api/inventory") { bearerAuth(tokenA) }.bodyAsText()
        )
        val resultB = json.decodeFromString<InventoryDto>(
            client.get("/api/inventory") { bearerAuth(tokenB) }.bodyAsText()
        )

        // Then: Jeder sieht nur seine eigenen Daten
        assertEquals(1, resultA.items.size)
        assertEquals("Dosenbrot", resultA.items[0].name)
        assertEquals("Konserven-A", resultA.categories[0].name)

        assertEquals(1, resultB.items.size)
        assertEquals("Seife", resultB.items[0].name)
        assertEquals("Hygiene-B", resultB.categories[0].name)
    }

    @Test
    fun test_deleteAdmin_selfDeletion_returns409() = testApp {
        // Given: Admin-Token
        val adminToken = createTestAccessToken(
            userId = TEST_ADMIN_ID,
            username = TEST_ADMIN_USERNAME,
            isAdmin = true
        )

        // When: Admin versucht sich selbst zu löschen
        val response = client.delete("/api/admin/users/$TEST_ADMIN_ID") { bearerAuth(adminToken) }

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