package de.krisenvorrat.app.data.db.dao

import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import de.krisenvorrat.app.data.db.KrisenvorratDatabase
import de.krisenvorrat.app.data.db.entity.CategoryEntity
import de.krisenvorrat.app.data.db.entity.ItemEntity
import de.krisenvorrat.app.data.db.entity.LocationEntity
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.time.LocalDate

@RunWith(AndroidJUnit4::class)
internal class ItemDaoTest {

    private lateinit var db: KrisenvorratDatabase
    private lateinit var dao: ItemDao

    @Before
    fun setup() {
        db = Room.inMemoryDatabaseBuilder(
            ApplicationProvider.getApplicationContext(),
            KrisenvorratDatabase::class.java
        ).allowMainThreadQueries().build()
        dao = db.itemDao()
        runBlocking {
            db.categoryDao().insert(CategoryEntity(id = 1, name = "Kategorie"))
            db.locationDao().insert(LocationEntity(id = 1, name = "Ort"))
        }
    }

    @After
    fun teardown() {
        db.close()
    }

    private fun buildItem(
        id: String = "item-1",
        categoryId: Int = 1,
        locationId: Int = 1,
        quantity: Double = 1.0,
        expiryDate: LocalDate? = null
    ) = ItemEntity(
        id = id,
        name = "Wasser",
        categoryId = categoryId,
        quantity = quantity,
        unit = "Liter",
        unitPrice = 0.5,
        kcalPer100g = null,
        expiryDate = expiryDate,
        locationId = locationId,
        minStock = 0.0,
        notes = "",
        lastUpdated = System.currentTimeMillis()
    )

    @Test
    fun test_insert_emptyDb_getAllReturnsOneItem() = runBlocking {
        // Given
        val item = buildItem()

        // When
        dao.insert(item)

        // Then
        val result = dao.getAll().first()
        assertEquals(1, result.size)
        assertEquals("item-1", result.first().id)
    }

    @Test
    fun test_update_existingItem_quantityIsChanged() = runBlocking {
        // Given
        dao.insert(buildItem(quantity = 1.0))

        // When
        dao.update(buildItem(quantity = 5.0))

        // Then
        val updated = dao.getById("item-1")
        assertEquals(5.0, updated?.quantity)
    }

    @Test
    fun test_delete_existingItem_getAllReturnsEmpty() = runBlocking {
        // Given
        val item = buildItem()
        dao.insert(item)

        // When
        dao.delete(item)

        // Then
        val result = dao.getAll().first()
        assertEquals(0, result.size)
    }

    @Test
    fun test_getByLocation_twoItemsDifferentLocations_returnsOnlyMatching() = runBlocking {
        // Given
        db.locationDao().insert(LocationEntity(id = 2, name = "Anderer Ort"))
        dao.insert(buildItem(id = "item-1", locationId = 1))
        dao.insert(buildItem(id = "item-2", locationId = 2))

        // When
        val result = dao.getByLocation(1).first()

        // Then
        assertEquals(1, result.size)
        assertEquals("item-1", result.first().id)
    }

    @Test
    fun test_getByCategory_twoItemsDifferentCategories_returnsOnlyMatching() = runBlocking {
        // Given
        db.categoryDao().insert(CategoryEntity(id = 2, name = "Andere"))
        dao.insert(buildItem(id = "item-1", categoryId = 1))
        dao.insert(buildItem(id = "item-2", categoryId = 2))

        // When
        val result = dao.getByCategory(1).first()

        // Then
        assertEquals(1, result.size)
        assertEquals("item-1", result.first().id)
    }

    @Test
    fun test_getExpiringSoon_itemExpiresInOneDayWithCutoff365Days_returnsItem() = runBlocking {
        // Given
        val tomorrow = LocalDate.now().plusDays(1)
        dao.insert(buildItem(expiryDate = tomorrow))

        // When
        val cutoff = LocalDate.now().plusDays(365)
        val result = dao.getExpiringSoonByCutoff(cutoff).first()

        // Then
        assertEquals(1, result.size)
        assertEquals("item-1", result.first().id)
    }

    @Test
    fun test_getAll_multipleItems_returnsAll() = runBlocking {
        // Given
        dao.insert(buildItem(id = "item-1"))
        dao.insert(buildItem(id = "item-2"))
        dao.insert(buildItem(id = "item-3"))

        // When
        val result = dao.getAll().first()

        // Then
        assertEquals(3, result.size)
    }

    @Test
    fun test_getById_existingItem_returnsItem() = runBlocking {
        // Given
        dao.insert(buildItem(id = "item-1"))

        // When
        val result = dao.getById("item-1")

        // Then
        assertEquals("item-1", result?.id)
        assertEquals("Wasser", result?.name)
    }

    @Test
    fun test_getById_nonExistentId_returnsNull() = runBlocking {
        // Given / When
        val result = dao.getById("does-not-exist")

        // Then
        assertNull(result)
    }
}
