package de.krisenvorrat.app.ui.item

import androidx.lifecycle.SavedStateHandle
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 de.krisenvorrat.app.domain.model.ItemFormPrefill
import de.krisenvorrat.app.domain.repository.CategoryRepository
import de.krisenvorrat.app.domain.repository.ItemRepository
import de.krisenvorrat.app.domain.repository.LocationRepository
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 kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.time.LocalDate

@OptIn(ExperimentalCoroutinesApi::class)
class ItemFormViewModelTest {

    private val testDispatcher = StandardTestDispatcher()
    private lateinit var fakeItemRepository: FakeItemFormRepository
    private lateinit var fakeCategoryRepository: FakeFormCategoryRepository
    private lateinit var fakeLocationRepository: FakeFormLocationRepository

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        fakeItemRepository = FakeItemFormRepository()
        fakeCategoryRepository = FakeFormCategoryRepository()
        fakeLocationRepository = FakeFormLocationRepository()
    }

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

    private fun createViewModel(itemId: String? = null): ItemFormViewModel {
        val savedStateHandle = SavedStateHandle().apply {
            if (itemId != null) set("itemId", itemId)
        }
        return ItemFormViewModel(
            itemRepository = fakeItemRepository,
            categoryRepository = fakeCategoryRepository,
            locationRepository = fakeLocationRepository,
            savedStateHandle = savedStateHandle
        )
    }

    // --- Create Mode Tests ---

    @Test
    fun test_init_createMode_stateIsEmpty() = runTest(testDispatcher) {
        // Given / When
        val viewModel = createViewModel()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isEditMode)
        assertEquals("", state.name)
        assertEquals(null, state.categoryId)
        assertEquals("", state.quantity)
        assertEquals(null, state.locationId)
        assertFalse(state.isSaved)
    }

    @Test
    fun test_init_createMode_loadsCategories() = runTest(testDispatcher) {
        // Given
        fakeCategoryRepository.emit(
            listOf(
                CategoryEntity(id = 1, name = "Lebensmittel"),
                CategoryEntity(id = 2, name = "Hygiene")
            )
        )

        // When
        val viewModel = createViewModel()
        advanceUntilIdle()

        // Then
        assertEquals(2, viewModel.uiState.value.categories.size)
    }

    @Test
    fun test_init_createMode_loadsLocations() = runTest(testDispatcher) {
        // Given
        fakeLocationRepository.emit(
            listOf(
                LocationEntity(id = 1, name = "Keller"),
                LocationEntity(id = 2, name = "Küche")
            )
        )

        // When
        val viewModel = createViewModel()
        advanceUntilIdle()

        // Then
        assertEquals(2, viewModel.uiState.value.locations.size)
    }

    @Test
    fun test_init_createMode_singleLocation_autoSelectsLocation() = runTest(testDispatcher) {
        // Given
        fakeLocationRepository.emit(listOf(LocationEntity(id = 1, name = "Keller")))

        // When
        val viewModel = createViewModel()
        advanceUntilIdle()

        // Then
        assertEquals(1, viewModel.uiState.value.locationId)
    }

    @Test
    fun test_init_createMode_multipleLocations_doesNotAutoSelect() = runTest(testDispatcher) {
        // Given
        fakeLocationRepository.emit(
            listOf(
                LocationEntity(id = 1, name = "Keller"),
                LocationEntity(id = 2, name = "Garage")
            )
        )

        // When
        val viewModel = createViewModel()
        advanceUntilIdle()

        // Then
        assertEquals(null, viewModel.uiState.value.locationId)
    }

    @Test
    fun test_init_createMode_withLastUsedLocation_preselectsLastLocation() = runTest(testDispatcher) {
        // Given – an existing item with locationId = 2
        fakeItemRepository.addItem(
            ItemEntity(
                id = "prev-1", name = "Alt", categoryId = 1, quantity = 1.0,
                unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null,
                locationId = 2, notes = "", lastUpdated = 1000L
            )
        )
        fakeLocationRepository.emit(
            listOf(
                LocationEntity(id = 1, name = "Keller"),
                LocationEntity(id = 2, name = "Küche")
            )
        )

        // When
        val viewModel = createViewModel()
        advanceUntilIdle()

        // Then – location 2 is preselected (from last used item)
        assertEquals(2, viewModel.uiState.value.locationId)
    }

    @Test
    fun test_init_createMode_staleLastLocationId_doesNotPreselect() = runTest(testDispatcher) {
        // Given – last used location is 99, but only locations 1 and 2 exist
        fakeItemRepository.addItem(
            ItemEntity(
                id = "prev-1", name = "Alt", categoryId = 1, quantity = 1.0,
                unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null,
                locationId = 99, notes = "", lastUpdated = 1000L
            )
        )
        fakeLocationRepository.emit(
            listOf(
                LocationEntity(id = 1, name = "Keller"),
                LocationEntity(id = 2, name = "Küche")
            )
        )

        // When
        val viewModel = createViewModel()
        advanceUntilIdle()

        // Then – stale ID 99 is not in locations list → no preselection
        assertEquals(null, viewModel.uiState.value.locationId)
    }

    // --- Edit Mode Tests ---

    @Test
    fun test_init_editMode_loadsExistingItem() = runTest(testDispatcher) {
        // Given
        val item = ItemEntity(
            id = "edit-1",
            name = "Konserve",
            categoryId = 1,
            quantity = 5.0,
            unit = "Stk",
            unitPrice = 2.5,
            kcalPerKg = 120,
            expiryDate = LocalDate.of(2026, 12, 31),
            locationId = 2,
            notes = "Bohnen",
            lastUpdated = 1000L
        )
        fakeItemRepository.addItem(item)

        // When
        val viewModel = createViewModel(itemId = "edit-1")
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.isEditMode)
        assertEquals("edit-1", state.itemId)
        assertEquals("Konserve", state.name)
        assertEquals(1, state.categoryId)
        assertEquals("5", state.quantity)
        assertEquals("Stk", state.unit)
        assertEquals("2.5", state.unitPrice)
        assertEquals("120", state.kcalPerKg)
        assertEquals(LocalDate.of(2026, 12, 31), state.expiryDate)
        assertEquals(2, state.locationId)
        assertEquals("Bohnen", state.notes)
    }

    @Test
    fun test_init_editMode_withUnknownId_staysInCreateMode() = runTest(testDispatcher) {
        // Given – no item with this ID exists

        // When
        val viewModel = createViewModel(itemId = "nonexistent")
        advanceUntilIdle()

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

    // --- Field Update Tests ---

    @Test
    fun test_updateName_updatesState() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.updateName("Reis")

        // Then
        assertEquals("Reis", viewModel.uiState.value.name)
    }

    @Test
    fun test_updateCategoryId_updatesState() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.updateCategoryId(3)

        // Then
        assertEquals(3, viewModel.uiState.value.categoryId)
    }

    @Test
    fun test_updateQuantity_updatesState() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.updateQuantity("10.5")

        // Then
        assertEquals("10.5", viewModel.uiState.value.quantity)
    }

    @Test
    fun test_updateExpiryDate_updatesState() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()
        // Picker always stores last day of selected month
        val date = LocalDate.of(2027, 6, 30)

        // When
        viewModel.updateExpiryDate(date)

        // Then
        assertEquals(date, viewModel.uiState.value.expiryDate)
    }

    // --- Validation Tests ---

    @Test
    fun test_save_withEmptyName_showsValidationError() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.updateQuantity("5")
        viewModel.updateCategoryId(1)
        viewModel.updateLocationId(1)

        // When
        viewModel.save()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.validationErrors.containsKey("name"))
        assertFalse(viewModel.uiState.value.isSaved)
    }

    @Test
    fun test_save_withZeroQuantity_showsValidationError() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.updateName("Reis")
        viewModel.updateQuantity("0")
        viewModel.updateCategoryId(1)
        viewModel.updateLocationId(1)

        // When
        viewModel.save()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.validationErrors.containsKey("quantity"))
        assertFalse(viewModel.uiState.value.isSaved)
    }

    @Test
    fun test_save_withInvalidQuantity_showsValidationError() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.updateName("Reis")
        viewModel.updateQuantity("abc")
        viewModel.updateCategoryId(1)
        viewModel.updateLocationId(1)

        // When
        viewModel.save()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.validationErrors.containsKey("quantity"))
    }

    @Test
    fun test_save_withoutCategory_showsValidationError() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.updateName("Reis")
        viewModel.updateQuantity("5")
        viewModel.updateLocationId(1)

        // When
        viewModel.save()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.validationErrors.containsKey("categoryId"))
    }

    @Test
    fun test_save_withoutLocation_showsValidationError() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.updateName("Reis")
        viewModel.updateQuantity("5")
        viewModel.updateCategoryId(1)

        // When
        viewModel.save()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.validationErrors.containsKey("locationId"))
    }

    @Test
    fun test_save_withMultipleMissingFields_showsAllErrors() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()

        // When – all required fields are empty
        viewModel.save()
        advanceUntilIdle()

        // Then
        val errors = viewModel.uiState.value.validationErrors
        assertTrue(errors.containsKey("name"))
        assertTrue(errors.containsKey("quantity"))
        assertTrue(errors.containsKey("categoryId"))
        assertTrue(errors.containsKey("locationId"))
    }

    @Test
    fun test_updateName_clearsNameValidationError() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.save() // triggers validation errors

        // When
        viewModel.updateName("Reis")

        // Then
        assertFalse(viewModel.uiState.value.validationErrors.containsKey("name"))
    }

    // --- Save Tests ---

    @Test
    fun test_save_createMode_withValidData_insertsItem() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.updateName("Reis")
        viewModel.updateQuantity("5")
        viewModel.updateCategoryId(1)
        viewModel.updateLocationId(2)
        viewModel.updateUnit("kg")

        // When
        viewModel.save()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.isSaved)
        assertEquals(1, fakeItemRepository.insertedItems.size)
        val inserted = fakeItemRepository.insertedItems.first()
        assertEquals("Reis", inserted.name)
        assertEquals(5.0, inserted.quantity, 0.001)
        assertEquals(1, inserted.categoryId)
        assertEquals(2, inserted.locationId)
        assertEquals("kg", inserted.unit)
        assertNotNull(inserted.id) // UUID was generated
    }

    @Test
    fun test_save_editMode_withValidData_updatesItem() = runTest(testDispatcher) {
        // Given
        val item = ItemEntity(
            id = "upd-1",
            name = "Alt",
            categoryId = 1,
            quantity = 2.0,
            unit = "Stk",
            unitPrice = 0.0,
            kcalPerKg = null,
            expiryDate = null,
            locationId = 1,
            notes = "",
            lastUpdated = 0L
        )
        fakeItemRepository.addItem(item)
        val viewModel = createViewModel(itemId = "upd-1")
        advanceUntilIdle()

        // When
        viewModel.updateName("Neu")
        viewModel.updateQuantity("10")
        viewModel.save()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.isSaved)
        assertEquals(1, fakeItemRepository.updatedItems.size)
        val updated = fakeItemRepository.updatedItems.first()
        assertEquals("upd-1", updated.id)
        assertEquals("Neu", updated.name)
        assertEquals(10.0, updated.quantity, 0.001)
    }

    @Test
    fun test_save_createMode_doesNotUseEditModeId() = runTest(testDispatcher) {
        // Given
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.updateName("Test")
        viewModel.updateQuantity("1")
        viewModel.updateCategoryId(1)
        viewModel.updateLocationId(1)

        // When
        viewModel.save()
        advanceUntilIdle()

        // Then
        val inserted = fakeItemRepository.insertedItems.first()
        assertTrue(inserted.id.isNotBlank())
        assertTrue(fakeItemRepository.updatedItems.isEmpty())
    }

    // --- Prefill Tests ---

    @Test
    fun test_applyPrefill_validJson_setsNameAndUnit() = runTest(testDispatcher) {
        // Given
        val prefill = ItemFormPrefill(name = "Salz", unit = "kg")
        val json = Json.encodeToString(ItemFormPrefill.serializer(), prefill)
        val savedStateHandle = SavedStateHandle().apply { set("prefillJson", json) }
        val viewModel = ItemFormViewModel(
            itemRepository = fakeItemRepository,
            categoryRepository = fakeCategoryRepository,
            locationRepository = fakeLocationRepository,
            savedStateHandle = savedStateHandle
        )

        // When
        advanceUntilIdle()

        // Then
        assertEquals("Salz", viewModel.uiState.value.name)
        assertEquals("kg", viewModel.uiState.value.unit)
    }
}

// region Test Fakes

private class FakeItemFormRepository : ItemRepository {
    private val items = mutableListOf<ItemEntity>()
    val insertedItems = mutableListOf<ItemEntity>()
    val updatedItems = mutableListOf<ItemEntity>()

    fun addItem(item: ItemEntity) {
        items.add(item)
    }

    override fun getAll(): Flow<List<ItemEntity>> = MutableStateFlow(items.toList())

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

    override suspend fun insert(item: ItemEntity) {
        insertedItems.add(item)
    }

    override suspend fun update(item: ItemEntity) {
        updatedItems.add(item)
    }

    override suspend fun delete(item: ItemEntity) {
        items.remove(item)
    }

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

    override fun getByLocation(locationId: Int): Flow<List<ItemEntity>> =
        MutableStateFlow(items.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? = items.maxByOrNull { it.lastUpdated }?.locationId
}

private class FakeFormCategoryRepository : CategoryRepository {
    private val flow = MutableStateFlow<List<CategoryEntity>>(emptyList())

    fun emit(categories: List<CategoryEntity>) {
        flow.value = categories
    }

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

    override suspend fun insert(category: CategoryEntity) {
        flow.value = flow.value + category
    }

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

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

private class FakeFormLocationRepository : LocationRepository {
    private val flow = MutableStateFlow<List<LocationEntity>>(emptyList())

    fun emit(locations: List<LocationEntity>) {
        flow.value = locations
    }

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

    override suspend fun insert(location: LocationEntity) {
        flow.value = flow.value + location
    }

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

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

// endregion
