package de.krisenvorrat.app.ui.location

import de.krisenvorrat.app.data.db.entity.ItemEntity
import de.krisenvorrat.app.data.db.entity.LocationEntity
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 org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

@OptIn(ExperimentalCoroutinesApi::class)
class LocationListViewModelTest {

    private val testDispatcher = StandardTestDispatcher()
    private lateinit var fakeRepository: FakeLocationRepository
    private lateinit var fakeItemRepository: FakeItemRepositoryForLocations
    private lateinit var viewModel: LocationListViewModel

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        fakeRepository = FakeLocationRepository()
        fakeItemRepository = FakeItemRepositoryForLocations()
        viewModel = LocationListViewModel(fakeRepository, fakeItemRepository)
    }

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

    @Test
    fun test_init_withEmptyRepository_locationsListIsEmpty() = runTest(testDispatcher) {
        // Given – fresh ViewModel

        // When
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.locations.isEmpty())
    }

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

        // When
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertEquals(1, state.locations.size)
        assertEquals("Keller", state.locations.first().name)
    }

    @Test
    fun test_showAddDialog_setsIsAddDialogVisibleToTrue() = runTest(testDispatcher) {
        // Given
        advanceUntilIdle()

        // When
        viewModel.showAddDialog()

        // Then
        assertTrue(viewModel.uiState.value.isAddDialogVisible)
        assertEquals("", viewModel.uiState.value.newLocationName)
    }

    @Test
    fun test_dismissAddDialog_setsIsAddDialogVisibleToFalse() = runTest(testDispatcher) {
        // Given
        viewModel.showAddDialog()
        advanceUntilIdle()

        // When
        viewModel.dismissAddDialog()

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

    @Test
    fun test_updateNewLocationName_updatesStateCorrectly() = runTest(testDispatcher) {
        // Given
        viewModel.showAddDialog()
        advanceUntilIdle()

        // When
        viewModel.updateNewLocationName("Dachboden")

        // Then
        assertEquals("Dachboden", viewModel.uiState.value.newLocationName)
    }

    @Test
    fun test_addLocation_withValidName_insertsAndClosesDialog() = runTest(testDispatcher) {
        // Given
        viewModel.showAddDialog()
        viewModel.updateNewLocationName("Dachboden")
        advanceUntilIdle()

        // When
        viewModel.addLocation()
        advanceUntilIdle()

        // Then
        assertFalse(viewModel.uiState.value.isAddDialogVisible)
        assertEquals("", viewModel.uiState.value.newLocationName)
        assertEquals(1, fakeRepository.insertedLocations.size)
        assertEquals("Dachboden", fakeRepository.insertedLocations.first().name)
    }

    @Test
    fun test_addLocation_withBlankName_doesNotInsert() = runTest(testDispatcher) {
        // Given
        viewModel.showAddDialog()
        viewModel.updateNewLocationName("   ")
        advanceUntilIdle()

        // When
        viewModel.addLocation()
        advanceUntilIdle()

        // Then
        assertTrue(fakeRepository.insertedLocations.isEmpty())
    }

    @Test
    fun test_showDeleteDialog_setsDialogStateCorrectly() = runTest(testDispatcher) {
        // Given
        val location = LocationEntity(id = 1, name = "Keller")
        advanceUntilIdle()

        // When
        viewModel.showDeleteDialog(location)
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.isDeleteDialogVisible)
        assertEquals(location, state.locationToDelete)
    }

    @Test
    fun test_dismissDeleteDialog_clearsDialogState() = runTest(testDispatcher) {
        // Given
        viewModel.showDeleteDialog(LocationEntity(id = 1, name = "Test"))
        advanceUntilIdle()

        // When
        viewModel.dismissDeleteDialog()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isDeleteDialogVisible)
        assertNull(state.locationToDelete)
    }

    @Test
    fun test_deleteLocation_withSelectedLocation_deletesAndClosesDialog() = runTest(testDispatcher) {
        // Given
        val location = LocationEntity(id = 1, name = "Keller")
        viewModel.showDeleteDialog(location)
        advanceUntilIdle()

        // When
        viewModel.deleteLocation()
        advanceUntilIdle()

        // Then
        assertFalse(viewModel.uiState.value.isDeleteDialogVisible)
        assertNull(viewModel.uiState.value.locationToDelete)
        assertEquals(1, fakeRepository.deletedLocations.size)
        assertEquals(location, fakeRepository.deletedLocations.first())
    }

    @Test
    fun test_deleteLocation_withNoSelection_doesNothing() = runTest(testDispatcher) {
        // Given – no location selected
        advanceUntilIdle()

        // When
        viewModel.deleteLocation()
        advanceUntilIdle()

        // Then
        assertTrue(fakeRepository.deletedLocations.isEmpty())
    }

    @Test
    fun test_showEditDialog_setsEditDialogState() = runTest(testDispatcher) {
        // Given
        val location = LocationEntity(id = 1, name = "Keller")
        advanceUntilIdle()

        // When
        viewModel.showEditDialog(location)

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.isEditDialogVisible)
        assertEquals(location, state.locationToEdit)
        assertEquals("Keller", state.editLocationName)
    }

    @Test
    fun test_dismissEditDialog_clearsEditDialogState() = runTest(testDispatcher) {
        // Given
        viewModel.showEditDialog(LocationEntity(id = 1, name = "Test"))
        advanceUntilIdle()

        // When
        viewModel.dismissEditDialog()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isEditDialogVisible)
        assertNull(state.locationToEdit)
        assertEquals("", state.editLocationName)
    }

    @Test
    fun test_saveEditLocation_withValidName_updatesAndClosesDialog() = runTest(testDispatcher) {
        // Given
        val location = LocationEntity(id = 1, name = "Alt")
        viewModel.showEditDialog(location)
        viewModel.updateEditLocationName("Neu")
        advanceUntilIdle()

        // When
        viewModel.saveEditLocation()
        advanceUntilIdle()

        // Then
        assertFalse(viewModel.uiState.value.isEditDialogVisible)
        assertNull(viewModel.uiState.value.locationToEdit)
        assertEquals(1, fakeRepository.updatedLocations.size)
        assertEquals("Neu", fakeRepository.updatedLocations.first().name)
    }

    @Test
    fun test_saveEditLocation_withBlankName_doesNotUpdate() = runTest(testDispatcher) {
        // Given
        val location = LocationEntity(id = 1, name = "Alt")
        viewModel.showEditDialog(location)
        viewModel.updateEditLocationName("   ")
        advanceUntilIdle()

        // When
        viewModel.saveEditLocation()
        advanceUntilIdle()

        // Then
        assertTrue(fakeRepository.updatedLocations.isEmpty())
    }

    @Test
    fun test_showDeleteDialog_unusedLocation_showsSimpleDeleteDialog() = runTest(testDispatcher) {
        // Given – no items in fake item repository
        val location = LocationEntity(id = 1, name = "Keller")
        advanceUntilIdle()

        // When
        viewModel.showDeleteDialog(location)
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.isDeleteDialogVisible)
        assertFalse(state.isReassignDialogVisible)
        assertEquals(location, state.locationToDelete)
    }

    @Test
    fun test_showDeleteDialog_usedLocation_showsReassignDialog() = runTest(testDispatcher) {
        // Given – item uses location 1
        fakeItemRepository.addItem(
            ItemEntity(
                id = "i1", name = "Reis", categoryId = 1, quantity = 1.0,
                unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null,
                locationId = 1, notes = "", lastUpdated = 0L
            )
        )
        val location = LocationEntity(id = 1, name = "Keller")
        advanceUntilIdle()

        // When
        viewModel.showDeleteDialog(location)
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isDeleteDialogVisible)
        assertTrue(state.isReassignDialogVisible)
        assertEquals(1, state.itemCountForDelete)
        assertEquals(location, state.locationToDelete)
    }

    @Test
    fun test_deleteWithReassignment_reassignsItemsAndDeletesLocation() = runTest(testDispatcher) {
        // Given
        fakeItemRepository.addItem(
            ItemEntity(
                id = "i1", name = "Reis", categoryId = 1, quantity = 1.0,
                unit = "kg", unitPrice = 0.0, kcalPerKg = null, expiryDate = null,
                locationId = 1, notes = "", lastUpdated = 0L
            )
        )
        val location = LocationEntity(id = 1, name = "Keller")
        viewModel.showDeleteDialog(location)
        advanceUntilIdle()
        viewModel.updateReassignTarget(2)

        // When
        viewModel.deleteWithReassignment()
        advanceUntilIdle()

        // Then
        assertFalse(viewModel.uiState.value.isReassignDialogVisible)
        assertNull(viewModel.uiState.value.locationToDelete)
        assertEquals(1, fakeItemRepository.reassignedLocations.size)
        assertEquals(1 to 2, fakeItemRepository.reassignedLocations.first())
        assertEquals(1, fakeRepository.deletedLocations.size)
    }

    @Test
    fun test_dismissReassignDialog_clearsAllReassignState() = runTest(testDispatcher) {
        // Given – set up reassign dialog state
        fakeItemRepository.addItem(
            ItemEntity(
                id = "i1", name = "Wasser", categoryId = 1, quantity = 1.0,
                unit = "Flasche", unitPrice = 0.0, kcalPerKg = null, expiryDate = null,
                locationId = 1, notes = "", lastUpdated = 0L
            )
        )
        viewModel.showDeleteDialog(LocationEntity(id = 1, name = "Keller"))
        advanceUntilIdle()
        viewModel.updateReassignTarget(2)

        // When
        viewModel.dismissReassignDialog()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isReassignDialogVisible)
        assertNull(state.locationToDelete)
        assertEquals(0, state.itemCountForDelete)
        assertNull(state.reassignTargetLocationId)
    }
}

private class FakeLocationRepository : LocationRepository {
    private val flow = MutableStateFlow<List<LocationEntity>>(emptyList())
    val insertedLocations = mutableListOf<LocationEntity>()
    val deletedLocations = mutableListOf<LocationEntity>()
    val updatedLocations = mutableListOf<LocationEntity>()

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

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

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

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

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

private class FakeItemRepositoryForLocations : ItemRepository {
    private val _items = mutableListOf<ItemEntity>()
    val reassignedLocations = mutableListOf<Pair<Int, Int>>()

    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) { _items.add(item) }
    override suspend fun update(item: ItemEntity) { _items.replaceAll { if (it.id == item.id) item else it } }
    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 = _items.count { it.categoryId == categoryId }
    override suspend fun reassignCategory(fromId: Int, toId: Int) {}
    override suspend fun countByLocationId(locationId: Int): Int = _items.count { it.locationId == locationId }
    override suspend fun reassignLocation(fromId: Int, toId: Int) {
        reassignedLocations.add(fromId to toId)
        _items.replaceAll { if (it.locationId == fromId) it.copy(locationId = toId) else it }
    }
    override suspend fun getLastUsedLocationId(): Int? = _items.maxByOrNull { it.lastUpdated }?.locationId
}
