package de.krisenvorrat.app.data.export

import de.krisenvorrat.app.data.db.entity.CategoryEntity
import de.krisenvorrat.app.data.db.entity.LocationEntity
import de.krisenvorrat.app.data.db.entity.SettingsEntity
import de.krisenvorrat.app.domain.model.AgeGroup
import de.krisenvorrat.app.domain.model.AgeGroupEntry
import de.krisenvorrat.app.domain.model.toJson
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import java.time.LocalDate

class ImportExportRepositoryImplTest {

    private fun buildRepository(
        categoryDao: FakeCategoryDao = FakeCategoryDao(),
        locationDao: FakeLocationDao = FakeLocationDao(),
        itemDao: FakeItemDao = FakeItemDao(),
        settingsDao: FakeSettingsDao = FakeSettingsDao()
    ) = ImportExportRepositoryImpl(
        categoryDao = categoryDao,
        locationDao = locationDao,
        itemDao = itemDao,
        settingsDao = settingsDao,
        transaction = passThroughTransaction
    )

    @Test
    fun test_exportToJson_withAllEntities_producesValidJson() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val locationDao = FakeLocationDao()
        val itemDao = FakeItemDao()
        val settingsDao = FakeSettingsDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Lebensmittel")))
        locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Keller")))
        itemDao.upsertAll(listOf(buildItemEntity("item1")))
        settingsDao.upsertAll(listOf(SettingsEntity(key = "theme", value = "dark")))
        val repository = buildRepository(categoryDao, locationDao, itemDao, settingsDao)

        // When
        val json = repository.exportToJson()

        // Then
        assertTrue(json.contains("\"version\":1"))
        assertTrue(json.contains("Lebensmittel"))
        assertTrue(json.contains("Keller"))
        assertTrue(json.contains("item1"))
        assertTrue(json.contains("dark"))
    }

    @Test
    fun test_exportToJson_withExpiryDate_storesIso8601String() = runBlocking {
        // Given
        val itemDao = FakeItemDao()
        itemDao.upsertAll(listOf(buildItemEntity("exp1").copy(expiryDate = LocalDate.of(2026, 12, 31))))
        val repository = buildRepository(itemDao = itemDao)

        // When
        val json = repository.exportToJson()

        // Then
        assertTrue(json.contains("\"2026-12-31\""))
    }

    @Test
    fun test_importFromJson_withValidJson_upsertAllEntities() = runBlocking {
        // Given
        val validJson = """{"version":1,"categories":[{"id":1,"name":"Lebensmittel"}],"locations":[{"id":1,"name":"Keller"}],"items":[{"id":"item1","name":"Konserve","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerKg":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":0}],"settings":[{"key":"theme","value":"dark"}]}"""
        val categoryDao = FakeCategoryDao()
        val locationDao = FakeLocationDao()
        val itemDao = FakeItemDao()
        val settingsDao = FakeSettingsDao()
        val repository = buildRepository(categoryDao, locationDao, itemDao, settingsDao)

        // When
        val result = repository.importFromJson(validJson)

        // Then
        assertTrue(result.isSuccess)
        assertEquals(1, categoryDao.getItems().size)
        assertEquals("Lebensmittel", categoryDao.getItems().first().name)
        assertEquals(1, locationDao.getItems().size)
        assertEquals("Keller", locationDao.getItems().first().name)
        assertEquals(1, itemDao.getItems().size)
        assertEquals("item1", itemDao.getItems().first().id)
        assertEquals(1, settingsDao.getItems().size)
        assertEquals("dark", settingsDao.getItems().first().value)
    }

    @Test
    fun test_importFromJson_withInvalidJson_returnsFailure() = runBlocking {
        // Given
        val invalidJson = "{ not valid json !!!"
        val repository = buildRepository()

        // When
        val result = repository.importFromJson(invalidJson)

        // Then
        assertTrue(result.isFailure)
    }

    @Test
    fun test_importFromJson_withUnsupportedVersion_returnsFailure() = runBlocking {
        // Given
        val json = """{"version":99,"categories":[],"locations":[],"items":[],"settings":[]}"""
        val repository = buildRepository()

        // When
        val result = repository.importFromJson(json)

        // Then
        assertTrue(result.isFailure)
        assertTrue(result.exceptionOrNull()?.message?.contains("99") == true)
    }

    @Test
    fun test_exportToMarkdown_withCategoriesAndItems_producesValidMarkdown() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val locationDao = FakeLocationDao()
        val itemDao = FakeItemDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Lebensmittel")))
        locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Keller")))
        itemDao.upsertAll(listOf(
            buildItemEntity("item1").copy(
                name = "Konserve",
                quantity = 5.0,
                unit = "Stk",
                expiryDate = LocalDate.of(2027, 3, 15),
                locationId = 1
            )
        ))
        val repository = buildRepository(categoryDao, locationDao, itemDao)

        // When
        val markdown = repository.exportToMarkdown()

        // Then
        assertTrue(markdown.contains("# Krisenvorrat Inventar"))
        assertTrue(markdown.contains("## Lebensmittel"))
        assertTrue(markdown.contains("| Konserve | 5 | Stk | 15.03.2027 | Keller |"))
    }

    @Test
    fun test_exportToMarkdown_withEmptyCategory_skipsCategory() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val itemDao = FakeItemDao()
        categoryDao.upsertAll(listOf(
            CategoryEntity(id = 1, name = "Lebensmittel"),
            CategoryEntity(id = 2, name = "Hygiene")
        ))
        itemDao.upsertAll(listOf(buildItemEntity("item1").copy(categoryId = 1)))
        val repository = buildRepository(categoryDao, itemDao = itemDao)

        // When
        val markdown = repository.exportToMarkdown()

        // Then
        assertTrue(markdown.contains("## Lebensmittel"))
        assertTrue(!markdown.contains("## Hygiene"))
    }

    @Test
    fun test_exportToMarkdown_withNoExpiryDate_showsDash() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val locationDao = FakeLocationDao()
        val itemDao = FakeItemDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Wasser")))
        locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Keller")))
        itemDao.upsertAll(listOf(
            buildItemEntity("item1").copy(name = "Flasche", expiryDate = null)
        ))
        val repository = buildRepository(categoryDao, locationDao, itemDao)

        // When
        val markdown = repository.exportToMarkdown()

        // Then
        assertTrue(markdown.contains("| Flasche | 2 | Stk | – | Keller |"))
    }

    @Test
    fun test_exportToMarkdown_withSettings_includesSettingsSection() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val settingsDao = FakeSettingsDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Test")))
        val ageGroups = listOf(
            AgeGroupEntry(ageGroup = AgeGroup.ERWACHSENER, count = 4, kcalPerDay = 2000)
        )
        settingsDao.upsertAll(listOf(
            SettingsEntity(key = "age_groups", value = ageGroups.toJson())
        ))
        val repository = buildRepository(categoryDao, settingsDao = settingsDao)

        // When
        val markdown = repository.exportToMarkdown()

        // Then
        assertTrue(markdown.contains("## Einstellungen"))
        assertTrue(markdown.contains("**Haushalt kcal/Tag:** 8000"))
    }

    @Test
    fun test_exportToMarkdown_withFractionalQuantity_formatsWithComma() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val locationDao = FakeLocationDao()
        val itemDao = FakeItemDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Lebensmittel")))
        locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Keller")))
        itemDao.upsertAll(listOf(
            buildItemEntity("item1").copy(name = "Reis", quantity = 2.5, unit = "kg")
        ))
        val repository = buildRepository(categoryDao, locationDao, itemDao)

        // When
        val markdown = repository.exportToMarkdown()

        // Then
        assertTrue(markdown.contains("| Reis | 2,5 | kg |"))
    }

    @Test
    fun test_exportToMarkdown_withNoSettingsRelevant_omitsSettingsSection() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val settingsDao = FakeSettingsDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Test")))
        settingsDao.upsertAll(listOf(SettingsEntity(key = "theme", value = "dark")))
        val repository = buildRepository(categoryDao, settingsDao = settingsDao)

        // When
        val markdown = repository.exportToMarkdown()

        // Then
        assertTrue(!markdown.contains("## Einstellungen"))
    }

    @Test
    fun test_applyInventoryDto_lastWriteWins_localNewerNotOverwritten() = runBlocking {
        // Given – lokales Item hat neueren Timestamp
        val itemDao = FakeItemDao()
        itemDao.upsertAll(listOf(buildItemEntity("item1").copy(name = "Lokal", lastUpdated = 1000L)))
        val repository = buildRepository(itemDao = itemDao)
        val json = """{"version":1,"categories":[],"locations":[],"items":[{"id":"item1","name":"VomServer","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerKg":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":500}],"settings":[]}"""

        // When
        repository.importFromJson(json)

        // Then – lokaler Name bleibt erhalten, da lokaler Timestamp neuer ist
        assertEquals("Lokal", itemDao.getItems().first { it.id == "item1" }.name)
    }

    @Test
    fun test_applyInventoryDto_lastWriteWins_serverNewerIsApplied() = runBlocking {
        // Given – Server-Item hat neueren Timestamp
        val itemDao = FakeItemDao()
        itemDao.upsertAll(listOf(buildItemEntity("item2").copy(name = "Lokal", lastUpdated = 100L)))
        val repository = buildRepository(itemDao = itemDao)
        val json = """{"version":1,"categories":[],"locations":[],"items":[{"id":"item2","name":"VomServer","categoryId":1,"quantity":2.0,"unit":"Stk","unitPrice":1.5,"kcalPerKg":null,"expiryDate":null,"locationId":1,"notes":"","lastUpdated":2000}],"settings":[]}"""

        // When
        repository.importFromJson(json)

        // Then – Server-Name wird übernommen, da Server-Timestamp neuer ist
        assertEquals("VomServer", itemDao.getItems().first { it.id == "item2" }.name)
    }

    // --- CSV Export Tests ---

    @Test
    fun test_exportToCsv_withItems_producesValidCsv() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val locationDao = FakeLocationDao()
        val itemDao = FakeItemDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Lebensmittel")))
        locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Keller")))
        itemDao.upsertAll(listOf(
            buildItemEntity("item1").copy(
                name = "Konserve",
                quantity = 5.0,
                unit = "Stk",
                unitPrice = 1.50,
                expiryDate = LocalDate.of(2027, 3, 15),
                locationId = 1
            )
        ))
        val repository = buildRepository(categoryDao, locationDao, itemDao)

        // When
        val csv = repository.exportToCsv()

        // Then
        assertTrue(csv.startsWith("\uFEFF"))
        assertTrue(csv.contains("Name;Kategorie;Menge;Einheit;Stückpreis;kcal/kg;MHD;Lagerort;Notizen"))
        assertTrue(csv.contains("Konserve;Lebensmittel;5;Stk;1,50;;15.03.2027;Keller;"))
    }

    @Test
    fun test_exportToCsv_withSemicolonInName_escapesWithQuotes() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val locationDao = FakeLocationDao()
        val itemDao = FakeItemDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Lebensmittel")))
        locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Keller")))
        itemDao.upsertAll(listOf(
            buildItemEntity("item1").copy(name = "Reis; Langkorn")
        ))
        val repository = buildRepository(categoryDao, locationDao, itemDao)

        // When
        val csv = repository.exportToCsv()

        // Then
        assertTrue(csv.contains("\"Reis; Langkorn\""))
    }

    @Test
    fun test_exportToCsv_withFractionalQuantity_usesGermanLocale() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val locationDao = FakeLocationDao()
        val itemDao = FakeItemDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Lebensmittel")))
        locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Keller")))
        itemDao.upsertAll(listOf(
            buildItemEntity("item1").copy(name = "Reis", quantity = 2.5, unit = "kg")
        ))
        val repository = buildRepository(categoryDao, locationDao, itemDao)

        // When
        val csv = repository.exportToCsv()

        // Then
        assertTrue(csv.contains("2,5"))
    }

    @Test
    fun test_exportToCsv_empty_producesHeaderOnly() = runBlocking {
        // Given
        val repository = buildRepository()

        // When
        val csv = repository.exportToCsv()

        // Then
        assertTrue(csv.startsWith("\uFEFF"))
        val lines = csv.trim().lines()
        assertEquals(1, lines.size)
        assertTrue(lines[0].contains("Name;Kategorie"))
    }

    @Test
    fun test_exportToCsv_withKcalPerKg_includesValue() = runBlocking {
        // Given
        val categoryDao = FakeCategoryDao()
        val locationDao = FakeLocationDao()
        val itemDao = FakeItemDao()
        categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Lebensmittel")))
        locationDao.upsertAll(listOf(LocationEntity(id = 1, name = "Keller")))
        itemDao.upsertAll(listOf(
            buildItemEntity("item1").copy(name = "Reis", kcalPerKg = 3500)
        ))
        val repository = buildRepository(categoryDao, locationDao, itemDao)

        // When
        val csv = repository.exportToCsv()

        // Then
        assertTrue(csv.contains("3500"))
    }
}
