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 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

/**
 * End-to-End Sync-Tests: Simuliert den vollständigen Sync-Workflow
 * (App exportiert Inventar → Server speichert → andere App importiert).
 */
class EndToEndSyncTest {

    private val json = Json {
        ignoreUnknownKeys = true
        encodeDefaults = 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:e2e_test_${System.nanoTime()};DB_CLOSE_DELAY=-1",
                driver = "org.h2.Driver",
                adminPassword = "test-admin-pw"
            )
            configurePlugins()
        }
        block()
    }

    @Test
    fun test_fullSyncCycle_pushThenPull_roundTripsCorrectly() = testApp {
        // Given – "Client A" hat ein vollständiges Inventar
        val clientAInventory = createFullInventory()
        val body = json.encodeToString(InventoryDto.serializer(), clientAInventory)

        // When – Client A pusht (Upload)
        val pushResponse = client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(body)
        }

        // Then – Push erfolgreich
        assertEquals(HttpStatusCode.OK, pushResponse.status)

        // When – "Client B" pullt (Download)
        val pullResponse = client.get("/api/inventory") {
            bearerAuth(token)
        }

        // Then – Client B erhält exakt die Daten von Client A
        assertEquals(HttpStatusCode.OK, pullResponse.status)
        val pulledInventory = json.decodeFromString<InventoryDto>(pullResponse.bodyAsText())

        assertEquals(clientAInventory.categories.size, pulledInventory.categories.size)
        assertEquals(clientAInventory.locations.size, pulledInventory.locations.size)
        assertEquals(clientAInventory.items.size, pulledInventory.items.size)
        assertEquals(clientAInventory.settings.size, pulledInventory.settings.size)

        // Inhalte prüfen
        assertEquals("Konserven", pulledInventory.categories[0].name)
        assertEquals("Getränke", pulledInventory.categories[1].name)
        assertEquals("Keller", pulledInventory.locations[0].name)
        assertEquals("Dosenbrot", pulledInventory.items[0].name)
        assertEquals(5.0, pulledInventory.items[0].quantity, 0.01)
        assertEquals("dark", pulledInventory.settings[0].value)
    }

    @Test
    fun test_multiClientSync_secondClientOverwritesFirst() = testApp {
        // Given – Client A pusht
        val clientAInventory = createFullInventory()
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), clientAInventory))
        }

        // When – Client B pusht ein anderes Inventar (überschreibt)
        val clientBInventory = InventoryDto(
            categories = listOf(CategoryDto(id = 10, name = "Hygiene")),
            locations = listOf(LocationDto(id = 10, name = "Badezimmer")),
            items = listOf(
                ItemDto(
                    id = "item-b1",
                    name = "Seife",
                    categoryId = 10,
                    quantity = 3.0,
                    unit = "Stück",
                    unitPrice = 1.50,
                    kcalPerKg = null,
                    expiryDate = null,
                    locationId = 10,
                    notes = "",
                    lastUpdated = 1715100000L
                )
            ),
            settings = listOf(SettingDto(key = "lang", value = "de"))
        )
        val pushResponse = client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), clientBInventory))
        }
        assertEquals(HttpStatusCode.OK, pushResponse.status)

        // Then – Client C pullt und sieht nur Client B's Daten
        val pullResponse = client.get("/api/inventory") {
            bearerAuth(token)
        }
        val pulledInventory = json.decodeFromString<InventoryDto>(pullResponse.bodyAsText())

        assertEquals(1, pulledInventory.categories.size)
        assertEquals("Hygiene", pulledInventory.categories[0].name)
        assertEquals(1, pulledInventory.locations.size)
        assertEquals("Badezimmer", pulledInventory.locations[0].name)
        assertEquals(1, pulledInventory.items.size)
        assertEquals("Seife", pulledInventory.items[0].name)
    }

    @Test
    fun test_pullFromEmptyServer_returnsEmptyInventory() = testApp {
        // When – Pull ohne vorherigen Push
        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_pushPullPushPull_multipleRoundTrips() = testApp {
        // Round-Trip 1: Push + Pull
        val inventory1 = createFullInventory()
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), inventory1))
        }
        val pull1 = client.get("/api/inventory") { bearerAuth(token) }
        val result1 = json.decodeFromString<InventoryDto>(pull1.bodyAsText())
        assertEquals(2, result1.items.size)

        // Round-Trip 2: Aktualisiertes Inventar pushen
        val inventory2 = InventoryDto(
            categories = listOf(CategoryDto(id = 1, name = "Konserven")),
            locations = listOf(LocationDto(id = 1, name = "Keller")),
            items = listOf(
                ItemDto(
                    id = "item-1",
                    name = "Dosenbrot",
                    categoryId = 1,
                    quantity = 10.0,
                    unit = "Stück",
                    unitPrice = 3.99,
                    kcalPerKg = 250,
                    expiryDate = "2027-06-15",
                    locationId = 1,
                    notes = "Menge erhöht",
                    lastUpdated = 1715200000L
                )
            ),
            settings = emptyList()
        )
        client.put("/api/inventory") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), inventory2))
        }
        val pull2 = client.get("/api/inventory") { bearerAuth(token) }
        val result2 = json.decodeFromString<InventoryDto>(pull2.bodyAsText())

        assertEquals(1, result2.items.size)
        assertEquals(10.0, result2.items[0].quantity, 0.01)
        assertEquals("Menge erhöht", result2.items[0].notes)
    }

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

        // Then – Health-Endpoint braucht keinen API-Key
        assertEquals(HttpStatusCode.OK, response.status)
        assertEquals("OK", response.bodyAsText())
    }

    @Test
    fun test_pushWithoutAuth_returns401() = testApp {
        // When – Push ohne API-Key
        val response = client.put("/api/inventory") {
            contentType(ContentType.Application.Json)
            setBody(json.encodeToString(InventoryDto.serializer(), createFullInventory()))
        }

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

    @Test
    fun test_pullWithoutAuth_returns401() = testApp {
        // When – Pull ohne API-Key
        val response = client.get("/api/inventory")

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

    private fun createFullInventory(): 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
            ),
            ItemDto(
                id = "item-2",
                name = "Mineralwasser",
                categoryId = 2,
                quantity = 24.0,
                unit = "Liter",
                unitPrice = 0.49,
                kcalPerKg = 0,
                expiryDate = "2028-01-01",
                locationId = 2,
                notes = "Stilles Wasser",
                lastUpdated = 1715000000L
            )
        ),
        settings = listOf(
            SettingDto(key = "theme", value = "dark"),
            SettingDto(key = "household_size", value = "4")
        )
    )

    private companion object {
    }
}
