package de.krisenvorrat.server

import de.krisenvorrat.server.db.DatabaseFactory
import de.krisenvorrat.server.model.ErrorResponse
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 de.krisenvorrat.shared.model.SettingDto
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 ApplicationTest {

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

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

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        assertEquals("OK", response.bodyAsText())
    }

    @Test
    fun test_unknownRoute_returns404() = testApp {
        // When
        val response = client.get("/nonexistent")

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

    @Test
    fun test_getInventory_emptyDatabase_returnsEmptyInventory() = testApp {
        // When
        val response = client.get("/api/inventory") {
            bearerAuth(token)
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val inventory = json.decodeFromString<InventoryDto>(response.bodyAsText())
        assertTrue(inventory.categories.isEmpty())
        assertTrue(inventory.locations.isEmpty())
        assertTrue(inventory.items.isEmpty())
        assertTrue(inventory.settings.isEmpty())
    }

    @Test
    fun test_putInventory_validData_returnsUpdatedInventory() = testApp {
        // Given
        val inventory = createTestInventory()
        val body = json.encodeToString(InventoryDto.serializer(), inventory)

        // When
        val response = client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(body)
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val result = json.decodeFromString<InventoryDto>(response.bodyAsText())
        assertEquals(2, result.categories.size)
        assertEquals("Konserven", result.categories[0].name)
        assertEquals(1, result.items.size)
        assertEquals("Dosenbrot", result.items[0].name)
    }

    @Test
    fun test_putThenGet_roundTripsCorrectly() = testApp {
        // Given
        val inventory = createTestInventory()
        val body = json.encodeToString(InventoryDto.serializer(), inventory)

        // When – PUT
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(body)
        }

        // When – GET
        val response = client.get("/api/inventory") {
            bearerAuth(token)
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val result = json.decodeFromString<InventoryDto>(response.bodyAsText())
        assertEquals(2, result.categories.size)
        assertEquals(2, result.locations.size)
        assertEquals(1, result.items.size)
        assertEquals(1, result.settings.size)
        assertEquals("item-1", result.items[0].id)
    }

    @Test
    fun test_putInventory_invalidJson_returns400() = testApp {
        // When
        val response = client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody("{invalid json}")
        }

        // Then
        assertEquals(HttpStatusCode.BadRequest, response.status)
        val error = json.decodeFromString<ErrorResponse>(response.bodyAsText())
        assertEquals(400, error.status)
        assertTrue(error.message.contains("Invalid request body"))
    }

    @Test
    fun test_putInventory_replacesExistingData() = testApp {
        // Given – first PUT
        val firstInventory = createTestInventory()
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), firstInventory))
        }

        // When – second PUT with different data
        val updatedInventory = InventoryDto(
            categories = listOf(CategoryDto(id = 10, name = "Neu")),
            locations = listOf(LocationDto(id = 10, name = "Garage")),
            items = emptyList(),
            settings = listOf(SettingDto(key = "lang", value = "de"))
        )
        val response = client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), updatedInventory))
        }

        // Then
        assertEquals(HttpStatusCode.OK, response.status)
        val result = json.decodeFromString<InventoryDto>(response.bodyAsText())
        assertEquals(1, result.categories.size)
        assertEquals("Neu", result.categories[0].name)
        assertEquals(0, result.items.size)
    }

    @Test
    fun test_getInventory_returnsJsonContentType() = testApp {
        // When
        val response = client.get("/api/inventory") {
            bearerAuth(token)
        }

        // Then
        assertEquals(ContentType.Application.Json.withCharset(Charsets.UTF_8), response.contentType())
    }

    private fun createTestInventory(): InventoryDto = InventoryDto(
        categories = listOf(
            CategoryDto(id = 1, name = "Konserven"),
            CategoryDto(id = 2, name = "Getränke")
        ),
        locations = listOf(
            LocationDto(id = 1, name = "Keller"),
            LocationDto(id = 2, name = "Speisekammer")
        ),
        items = listOf(
            ItemDto(
                id = "item-1",
                name = "Dosenbrot",
                categoryId = 1,
                quantity = 5.0,
                unit = "Stück",
                unitPrice = 3.99,
                kcalPerKg = 250,
                expiryDate = "2027-06-15",
                locationId = 1,
                notes = "Vollkornbrot in der Dose",
                lastUpdated = 1715000000L
            )
        ),
        settings = listOf(
            SettingDto(key = "theme", value = "dark")
        )
    )
}
