package de.krisenvorrat.app.ui.warnings

import de.krisenvorrat.app.data.db.entity.ItemEntity
import de.krisenvorrat.app.domain.repository.ItemRepository
import de.krisenvorrat.app.domain.usecase.GetExpiryWarningsUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.time.LocalDate

@OptIn(ExperimentalCoroutinesApi::class)
class WarningsViewModelTest {

    private val testDispatcher = StandardTestDispatcher()
    private lateinit var fakeItemRepository: FakeItemRepository
    private lateinit var viewModel: WarningsViewModel

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        fakeItemRepository = FakeItemRepository()
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }

    private fun createViewModel() = WarningsViewModel(
        itemRepository = fakeItemRepository,
        getExpiryWarnings = GetExpiryWarningsUseCase()
    )

    @Test
    fun test_init_withNoData_stateHasEmptyLists() = runTest(testDispatcher) {
        // Given – empty repository
        viewModel = createViewModel()

        // When
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isLoading)
        assertFalse(state.hasExpiryWarnings)
        assertFalse(state.hasWarnings)
        assertEquals(0, state.totalWarningCount)
    }

    @Test
    fun test_init_withExpiringItems_expiryWarningsArePresent() = runTest(testDispatcher) {
        // Given – item expiring in 3 months (within URGENT_MONTHS=6)
        val expiryDate = LocalDate.now().plusMonths(3)
        fakeItemRepository.emit(
            listOf(buildTestItem(id = "a", expiryDate = expiryDate))
        )
        viewModel = createViewModel()

        // When
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.hasExpiryWarnings)
        assertEquals(1, state.expiryWarnings.size)
        assertTrue(state.hasWarnings)
        assertEquals(1, state.totalWarningCount)
    }

    @Test
    fun test_init_withFarExpiryDate_noExpiryWarnings() = runTest(testDispatcher) {
        // Given – item expiring in 2 years (beyond WARNING_MONTHS=12)
        val expiryDate = LocalDate.now().plusYears(2)
        fakeItemRepository.emit(
            listOf(buildTestItem(id = "a", expiryDate = expiryDate))
        )
        viewModel = createViewModel()

        // When
        advanceUntilIdle()

        // Then
        assertFalse(viewModel.uiState.value.hasExpiryWarnings)
    }

    @Test
    fun test_reactiveUpdate_whenItemsChange_stateUpdates() = runTest(testDispatcher) {
        // Given – start with no warnings
        viewModel = createViewModel()
        advanceUntilIdle()
        assertFalse(viewModel.uiState.value.hasWarnings)

        // When – an expiring item appears
        val expiryDate = LocalDate.now().plusMonths(3)
        fakeItemRepository.emit(
            listOf(buildTestItem(id = "a", expiryDate = expiryDate))
        )
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.hasExpiryWarnings)
        assertEquals(1, viewModel.uiState.value.totalWarningCount)
    }
}

// region Test Helpers

private fun buildTestItem(
    id: String = "id1",
    name: String = "Konserve",
    categoryId: Int = 1,
    quantity: Double = 1.0,
    unit: String = "Stk",
    unitPrice: Double = 0.0,
    kcalPerKg: Int? = null,
    expiryDate: LocalDate? = null,
    locationId: Int = 1
) = ItemEntity(
    id = id,
    name = name,
    categoryId = categoryId,
    quantity = quantity,
    unit = unit,
    unitPrice = unitPrice,
    kcalPerKg = kcalPerKg,
    expiryDate = expiryDate,
    locationId = locationId,
    notes = "",
    lastUpdated = 0L
)

private class FakeItemRepository : ItemRepository {
    private val flow = MutableStateFlow<List<ItemEntity>>(emptyList())

    fun emit(items: List<ItemEntity>) {
        flow.value = items
    }

    override fun getAll(): Flow<List<ItemEntity>> = flow

    override suspend fun getById(id: String): ItemEntity? =
        flow.value.find { it.id == id }

    override suspend fun insert(item: ItemEntity) {
        flow.value = flow.value + item
    }

    override suspend fun update(item: ItemEntity) {
        flow.value = flow.value.map { if (it.id == item.id) item else it }
    }

    override suspend fun delete(item: ItemEntity) {
        flow.value = flow.value.filter { it.id != item.id }
    }

    override fun getByCategory(categoryId: Int): Flow<List<ItemEntity>> =
        MutableStateFlow(flow.value.filter { it.categoryId == categoryId })

    override fun getByLocation(locationId: Int): Flow<List<ItemEntity>> =
        MutableStateFlow(flow.value.filter { it.locationId == locationId })

    override fun getExpiringSoon(daysUntil: Int): Flow<List<ItemEntity>> =
        MutableStateFlow(emptyList())

    override suspend fun countByCategoryId(categoryId: Int): Int = 0
    override suspend fun reassignCategory(fromId: Int, toId: Int) {}
    override suspend fun countByLocationId(locationId: Int): Int = 0
    override suspend fun reassignLocation(fromId: Int, toId: Int) {}
    override suspend fun getLastUsedLocationId(): Int? = null
}

// endregion
