package de.krisenvorrat.server

import de.krisenvorrat.server.db.DatabaseFactory
import de.krisenvorrat.server.model.ErrorResponse
import de.krisenvorrat.shared.model.CreateInventoryRequest
import de.krisenvorrat.shared.model.InventoryInfoDto
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.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test

class InventoryManagementTest {

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

    private suspend fun ApplicationTestBuilder.createUserAndGetToken(
        username: String, password: String = "pass123"
    ): Pair<String, String> {
        client.post("/api/admin/users") {
            bearerAuth(adminToken)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(mapOf("username" to username, "password" to password)))
        }
        val loginRes = client.post("/api/auth/login") {
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(mapOf("username" to username, "password" to password)))
        }
        val loginBody = json.parseToJsonElement(loginRes.bodyAsText())
        val obj = loginBody as kotlinx.serialization.json.JsonObject
        val token = (obj["accessToken"] as kotlinx.serialization.json.JsonPrimitive).content
        val userId = (obj["userId"] as kotlinx.serialization.json.JsonPrimitive).content
        return Pair(token, userId)
    }

    // --- Tests ---

    @Test
    fun test_listInventories_authenticated_returnsInventories() = testApp {
        // Given
        val (userToken, _) = createUserAndGetToken("invuser")

        // When
        val response = client.get("/api/inventories") {
            bearerAuth(userToken)
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val inventories = json.decodeFromString<List<InventoryInfoDto>>(response.bodyAsText())
        assertTrue(inventories.isNotEmpty())
        assertTrue(inventories.any { it.isActive })
    }

    @Test
    fun test_listInventories_unauthenticated_returns401() = testApp {
        // When
        val response = client.get("/api/inventories")

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

    @Test
    fun test_createInventory_validName_returnsCreated() = testApp {
        // Given
        val (userToken, _) = createUserAndGetToken("creator")

        // When
        val response = client.post("/api/inventories") {
            bearerAuth(userToken)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(CreateInventoryRequest("Keller-Vorrat")))
        }

        // Then
        assertEquals(HttpStatusCode.Created, response.status)
        val info = json.decodeFromString<InventoryInfoDto>(response.bodyAsText())
        assertEquals("Keller-Vorrat", info.name)
        assertTrue(info.isActive)
        assertNotNull(info.id)
    }

    @Test
    fun test_createInventory_emptyName_returns400() = testApp {
        // Given
        val (userToken, _) = createUserAndGetToken("emptycreator")

        // When
        val response = client.post("/api/inventories") {
            bearerAuth(userToken)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(CreateInventoryRequest("")))
        }

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

    @Test
    fun test_createInventory_switchesToNewInventory() = testApp {
        // Given
        val (userToken, _) = createUserAndGetToken("switchuser")

        // When – create a new inventory
        client.post("/api/inventories") {
            bearerAuth(userToken)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(CreateInventoryRequest("Büro")))
        }

        // Then – listing shows the new inventory as active
        val listResponse = client.get("/api/inventories") {
            bearerAuth(userToken)
        }
        val inventories = json.decodeFromString<List<InventoryInfoDto>>(listResponse.bodyAsText())
        val active = inventories.find { it.isActive }
        assertNotNull(active)
        assertEquals("Büro", active!!.name)
    }

    @Test
    fun test_switchInventory_existingInventory_switches() = testApp {
        // Given – create two users, each gets their own inventory
        val (user1Token, _) = createUserAndGetToken("user1")
        val (user2Token, _) = createUserAndGetToken("user2")

        // Get user2's inventory id
        val user2Inventories = json.decodeFromString<List<InventoryInfoDto>>(
            client.get("/api/inventories") { bearerAuth(user2Token) }.bodyAsText()
        )
        val user2InventoryId = user2Inventories.find { it.isActive }!!.id

        // When – user1 switches to user2's inventory
        val response = client.post("/api/inventories/$user2InventoryId/switch") {
            bearerAuth(user1Token)
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val info = json.decodeFromString<InventoryInfoDto>(response.bodyAsText())
        assertEquals(user2InventoryId, info.id)
        assertTrue(info.isActive)
    }

    @Test
    fun test_switchInventory_nonExistentInventory_returns404() = testApp {
        // Given
        val (userToken, _) = createUserAndGetToken("switcher")

        // When
        val response = client.post("/api/inventories/non-existent-id/switch") {
            bearerAuth(userToken)
        }

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

    @Test
    fun test_loginResponse_includesInventoryInfo() = testApp {
        // Given
        client.post("/api/admin/users") {
            bearerAuth(adminToken)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(mapOf("username" to "loginuser", "password" to "pass123")))
        }

        // When
        val response = client.post("/api/auth/login") {
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(mapOf("username" to "loginuser", "password" to "pass123")))
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val body = json.parseToJsonElement(response.bodyAsText()) as kotlinx.serialization.json.JsonObject
        assertTrue(body.containsKey("inventoryId"))
        assertFalse(body["inventoryId"].toString() == "null")
    }

    @Test
    fun test_createAndSwitch_inventoryDataIsolated() = testApp {
        // Given – user creates inventory with data, then switches to a new one
        val (userToken, _) = createUserAndGetToken("datauser")

        // Upload data to first inventory
        val inventory = de.krisenvorrat.shared.model.InventoryDto(
            categories = listOf(de.krisenvorrat.shared.model.CategoryDto(1, "Food")),
            locations = listOf(de.krisenvorrat.shared.model.LocationDto(1, "Cellar")),
            items = listOf(
                de.krisenvorrat.shared.model.ItemDto(
                    id = "item-1", name = "Water", categoryId = 1,
                    quantity = 10.0, unit = "L", unitPrice = 0.5,
                    kcalPerKg = null, expiryDate = null,
                    locationId = 1, notes = "", lastUpdated = 1000
                )
            ),
            settings = emptyList()
        )
        client.put("/api/inventory") {
            bearerAuth(userToken)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(inventory))
        }

        // Create new inventory
        client.post("/api/inventories") {
            bearerAuth(userToken)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(CreateInventoryRequest("Empty Inv")))
        }

        // When – get inventory (should be empty since we switched)
        val getResponse = client.get("/api/inventory") {
            bearerAuth(userToken)
        }

        // Then
        val loaded = json.decodeFromString<de.krisenvorrat.shared.model.InventoryDto>(getResponse.bodyAsText())
        assertTrue("New inventory should be empty", loaded.items.isEmpty())
    }
}
