package de.krisenvorrat.server.repository

import de.krisenvorrat.server.db.Categories
import de.krisenvorrat.server.db.DatabaseFactory
import de.krisenvorrat.server.db.DeletedItems
import de.krisenvorrat.server.db.Inventories
import de.krisenvorrat.server.db.Items
import de.krisenvorrat.server.db.Locations
import de.krisenvorrat.server.db.Settings
import de.krisenvorrat.server.db.Users
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 org.jetbrains.exposed.sql.deleteAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class InventoryRepositoryTest {

    private lateinit var repository: InventoryRepository

    private val testUserId = "repo-test-user"

    @Before
    fun setUp() {
        DatabaseFactory.init(
            jdbcUrl = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
            driver = "org.h2.Driver",
            adminPassword = "test-admin-pw"
        )
        repository = InventoryRepository()
        // Clear all data for a clean test environment
        transaction {
            DeletedItems.deleteAll()
            Items.deleteAll()
            Settings.deleteAll()
            Categories.deleteAll()
            Locations.deleteAll()
            Users.deleteAll()
            Inventories.deleteAll()
        }
    }

    @Test
    fun test_loadInventory_emptyDatabase_returnsEmptyInventory() {
        // When
        val result = repository.loadInventory(testUserId)

        // Then
        assertEquals(emptyList<CategoryDto>(), result.categories)
        assertEquals(emptyList<LocationDto>(), result.locations)
        assertEquals(emptyList<ItemDto>(), result.items)
        assertEquals(emptyList<SettingDto>(), result.settings)
    }

    @Test
    fun test_saveAndLoad_fullInventory_roundTripsCorrectly() {
        // Given
        val inventory = createTestInventory()

        // When
        repository.saveInventory(testUserId, inventory)
        val result = repository.loadInventory(testUserId)

        // Then
        assertEquals(2, result.categories.size)
        assertEquals("Konserven", result.categories[0].name)
        assertEquals("Getränke", result.categories[1].name)

        assertEquals(2, result.locations.size)
        assertEquals("Keller", result.locations[0].name)
        assertEquals("Speisekammer", result.locations[1].name)

        assertEquals(1, result.items.size)
        val item = result.items[0]
        assertEquals("item-1", item.id)
        assertEquals("Dosenbrot", item.name)
        assertEquals(1, item.categoryId)
        assertEquals(5.0, item.quantity, 0.001)
        assertEquals("Stück", item.unit)
        assertEquals(3.99, item.unitPrice, 0.001)
        assertEquals(250, item.kcalPerKg)
        assertEquals("2027-06-15", item.expiryDate)
        assertEquals(1, item.locationId)
        assertEquals("Vollkornbrot in der Dose", item.notes)
        assertEquals(1715000000L, item.lastUpdated)

        assertEquals(1, result.settings.size)
        assertEquals("theme", result.settings[0].key)
        assertEquals("dark", result.settings[0].value)
    }

    @Test
    fun test_saveInventory_overwritesExistingData() {
        // Given
        repository.saveInventory(testUserId, createTestInventory())

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

        // When
        repository.saveInventory(testUserId, updatedInventory)
        val result = repository.loadInventory(testUserId)

        // Then
        assertEquals(1, result.categories.size)
        assertEquals("Neu", result.categories[0].name)
        assertEquals(1, result.locations.size)
        assertEquals("Garage", result.locations[0].name)
        assertEquals(0, result.items.size)
        assertEquals(1, result.settings.size)
        assertEquals("lang", result.settings[0].key)
    }

    @Test
    fun test_saveAndLoad_itemWithNullableFieldsNull_roundTripsCorrectly() {
        // Given
        val inventory = InventoryDto(
            categories = listOf(CategoryDto(id = 1, name = "Sonstiges")),
            locations = listOf(LocationDto(id = 1, name = "Lager")),
            items = listOf(
                ItemDto(
                    id = "item-null",
                    name = "Kerzen",
                    categoryId = 1,
                    quantity = 10.0,
                    unit = "Stück",
                    unitPrice = 1.50,
                    kcalPerKg = null,
                    expiryDate = null,
                    locationId = 1,
                    notes = "",
                    lastUpdated = 1715000000L
                )
            ),
            settings = emptyList()
        )

        // When
        repository.saveInventory(testUserId, inventory)
        val result = repository.loadInventory(testUserId)

        // Then
        val item = result.items[0]
        assertNull(item.kcalPerKg)
        assertNull(item.expiryDate)
    }

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

    @Test
    fun test_getAggregatedStats_emptyDatabase_returnsZeros() {
        // When
        val stats = repository.getAggregatedStats()

        // Then
        assertEquals(0L, stats.totalItems)
        assertEquals(0L, stats.totalLocations)
        assertEquals(0L, stats.totalCategories)
        assertNull(stats.lastUpdated)
        assertEquals(0L, stats.recentTransactions)
    }

    @Test
    fun test_getAggregatedStats_withData_returnsCounts() {
        // Given
        repository.saveInventory(testUserId, createTestInventory())

        // When
        val stats = repository.getAggregatedStats()

        // Then
        assertEquals(1L, stats.totalItems)
        assertEquals(2L, stats.totalLocations)
        assertEquals(2L, stats.totalCategories)
        assertEquals(1715000000L, stats.lastUpdated)
    }

    @Test
    fun test_getAggregatedStats_recentTransactions_countsOnlyLast30Days() {
        // Given
        val now = System.currentTimeMillis()
        val oldTimestamp = now - 31L * 24 * 60 * 60 * 1000 // 31 days ago
        val recentTimestamp = now - 5L * 24 * 60 * 60 * 1000 // 5 days ago
        val inventory = InventoryDto(
            categories = listOf(CategoryDto(id = 1, name = "Test")),
            locations = listOf(LocationDto(id = 1, name = "Test")),
            items = listOf(
                ItemDto(id = "old", name = "Old", categoryId = 1, quantity = 1.0, unit = "Stk", unitPrice = 0.0,
                    kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = oldTimestamp),
                ItemDto(id = "recent1", name = "Recent1", categoryId = 1, quantity = 1.0, unit = "Stk", unitPrice = 0.0,
                    kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = recentTimestamp),
                ItemDto(id = "recent2", name = "Recent2", categoryId = 1, quantity = 1.0, unit = "Stk", unitPrice = 0.0,
                    kcalPerKg = null, expiryDate = null, locationId = 1, notes = "", lastUpdated = now)
            ),
            settings = emptyList()
        )
        repository.saveInventory(testUserId, inventory)

        // When
        val stats = repository.getAggregatedStats()

        // Then
        assertEquals(3L, stats.totalItems)
        assertEquals(2L, stats.recentTransactions)
        assertTrue(stats.lastUpdated!! >= recentTimestamp)
    }

    @Test
    fun test_loadInventorySince_returnsOnlyChangedItems() {
        // Given
        val inventory = InventoryDto(
            categories = listOf(CategoryDto(id = 1, name = "Test")),
            locations = listOf(LocationDto(id = 1, name = "Test")),
            items = listOf(
                ItemDto(id = "old", name = "Old", categoryId = 1, quantity = 1.0, unit = "Stk",
                    unitPrice = 0.0, kcalPerKg = null, expiryDate = null, locationId = 1,
                    notes = "", lastUpdated = 1000L),
                ItemDto(id = "new", name = "New", categoryId = 1, quantity = 1.0, unit = "Stk",
                    unitPrice = 0.0, kcalPerKg = null, expiryDate = null, locationId = 1,
                    notes = "", lastUpdated = 3000L)
            ),
            settings = emptyList()
        )
        repository.saveInventory(testUserId, inventory)

        // When
        val result = repository.loadInventorySince(testUserId, 2000L)

        // Then – only the item with lastUpdated > 2000 is returned
        assertEquals(1, result.items.size)
        assertEquals("new", result.items[0].id)
        // Note: deletedItemIds may contain entries from setUp clearing previous test data
    }

    @Test
    fun test_deleteItem_trackedInDeletedItems() {
        // Given
        repository.saveInventory(testUserId, createTestInventory())

        // When
        val deleted = repository.deleteItem(testUserId, "item-1")

        // Then
        assertTrue(deleted)
        val delta = repository.loadInventorySince(testUserId, 0L)
        assertTrue(delta.deletedItemIds.contains("item-1"))
    }

    @Test
    fun test_saveInventory_tracksRemovedItemsAsDeleted() {
        // Given
        repository.saveInventory(testUserId, createTestInventory())

        // When – save with no items (all removed)
        val emptyInventory = InventoryDto(
            categories = emptyList(), locations = emptyList(),
            items = emptyList(), settings = emptyList()
        )
        repository.saveInventory(testUserId, emptyInventory)

        // Then
        val delta = repository.loadInventorySince(testUserId, 0L)
        assertTrue(delta.deletedItemIds.contains("item-1"))
    }

    @Test
    fun test_getStatsPerInventory_emptyDatabase_returnsEmptyList() {
        // setUp clears all tables, so no inventories exist
        val stats = repository.getStatsPerInventory()
        assertTrue(stats.isEmpty())
    }

    @Test
    fun test_getStatsPerInventory_withData_returnsCorrectCounts() {
        // Given – create a dedicated inventory so we control the data precisely
        val invId = repository.createInventory("Testvorrat")
        val inventory = InventoryDto(
            categories = listOf(CategoryDto(id = 1, name = "Kat1"), CategoryDto(id = 2, name = "Kat2")),
            locations = listOf(LocationDto(id = 1, name = "Keller")),
            items = listOf(
                ItemDto(
                    id = "inv-item-1", name = "Dosenbrot", categoryId = 1, quantity = 3.0,
                    unit = "Stk", unitPrice = 1.5, kcalPerKg = null, expiryDate = null,
                    locationId = 1, notes = "", lastUpdated = 1715000000L
                )
            ),
            settings = emptyList()
        )
        repository.saveInventory(invId, inventory)

        // When
        val statsList = repository.getStatsPerInventory()
        val stats = statsList.find { it.inventoryId == invId }

        // Then
        assertNotNull(stats)
        assertEquals("Testvorrat", stats!!.inventoryName)
        assertEquals(1L, stats.totalItems)
        assertEquals(1L, stats.totalLocations)
        assertEquals(2L, stats.totalCategories)
        assertEquals(1715000000L, stats.lastUpdated)
        // lastUpdated = 1715000000L is ~May 2024, more than 30 days ago
        assertEquals(0L, stats.recentTransactions)
    }

    @Test
    fun test_getStatsPerInventory_userCount_reflectsAssignedUsers() {
        // Given
        val invId = repository.createInventory("UserCountTest")
        val userRepo = UserRepository()
        val uid1 = "stats-user-1"
        val uid2 = "stats-user-2"
        userRepo.create(uid1, "statsuser1", "hash1")
        userRepo.create(uid2, "statsuser2", "hash2")
        repository.assignUserToInventory(uid1, invId)
        repository.assignUserToInventory(uid2, invId)

        // When
        val stats = repository.getStatsPerInventory().find { it.inventoryId == invId }

        // Then
        assertNotNull(stats)
        assertEquals(2L, stats!!.userCount)
    }
}
