package de.krisenvorrat.app.ui.inventory

import de.krisenvorrat.app.data.db.entity.SettingsEntity
import de.krisenvorrat.app.domain.model.SettingsKey
import de.krisenvorrat.app.domain.model.SettingsKeys
import de.krisenvorrat.app.domain.model.SyncError
import de.krisenvorrat.app.domain.repository.ImportExportRepository
import de.krisenvorrat.app.domain.repository.SettingsRepository
import de.krisenvorrat.app.domain.repository.SyncService
import de.krisenvorrat.shared.model.InventoryDto
import de.krisenvorrat.shared.model.InventoryInfoDto
import de.krisenvorrat.shared.model.ItemDto
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 InventoryPickerViewModelTest {

    private val testDispatcher = StandardTestDispatcher()
    private lateinit var fakeSettingsRepository: FakeSettingsRepository
    private lateinit var fakeSyncService: FakeSyncService
    private lateinit var fakeImportExportRepository: FakeImportExportRepository

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        fakeSettingsRepository = FakeSettingsRepository()
        fakeSyncService = FakeSyncService()
        fakeImportExportRepository = FakeImportExportRepository()
    }

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

    private fun createViewModel() = InventoryPickerViewModel(
        syncService = fakeSyncService,
        settingsRepository = fakeSettingsRepository,
        importExportRepository = fakeImportExportRepository
    )

    @Test
    fun test_init_loadsActiveInventoryName() = runTest(testDispatcher) {
        // Given
        fakeSettingsRepository.store[SettingsKeys.ACTIVE_INVENTORY_NAME] = "Keller-Vorrat"

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

        // Then
        assertEquals("Keller-Vorrat", viewModel.uiState.value.activeInventoryName)
    }

    @Test
    fun test_loadInventories_success_updatesState() = runTest(testDispatcher) {
        // Given
        val inventories = listOf(
            InventoryInfoDto(id = "inv-1", name = "Keller", isActive = true),
            InventoryInfoDto(id = "inv-2", name = "Büro", isActive = false)
        )
        fakeSyncService.listInventoriesResult = Result.success(inventories)
        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.loadInventories()
        advanceUntilIdle()

        // Then
        assertEquals(2, viewModel.uiState.value.inventories.size)
        assertFalse(viewModel.uiState.value.isLoading)
        assertNull(viewModel.uiState.value.error)
    }

    @Test
    fun test_loadInventories_failure_showsError() = runTest(testDispatcher) {
        // Given
        fakeSyncService.listInventoriesResult = Result.failure(SyncError.ConnectionError(Exception("offline")))
        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.loadInventories()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.inventories.isEmpty())
        assertFalse(viewModel.uiState.value.isLoading)
        assertTrue(viewModel.uiState.value.error != null)
    }

    @Test
    fun test_createInventory_success_updatesActiveInventory() = runTest(testDispatcher) {
        // Given
        fakeSyncService.createInventoryResult =
            Result.success(InventoryInfoDto(id = "new-id", name = "Ferienhaus", isActive = true))
        fakeSyncService.listInventoriesResult = Result.success(
            listOf(InventoryInfoDto(id = "new-id", name = "Ferienhaus", isActive = true))
        )
        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.showCreateDialog()
        viewModel.onNewInventoryNameChanged("Ferienhaus")
        viewModel.createInventory()
        advanceUntilIdle()

        // Then
        assertEquals("Ferienhaus", viewModel.uiState.value.activeInventoryName)
        assertEquals("new-id", fakeSettingsRepository.store[SettingsKeys.ACTIVE_INVENTORY_ID])
        assertEquals("Ferienhaus", fakeSettingsRepository.store[SettingsKeys.ACTIVE_INVENTORY_NAME])
        assertFalse(viewModel.uiState.value.isCreateDialogVisible)
    }

    @Test
    fun test_switchInventory_success_updatesActiveInventory() = runTest(testDispatcher) {
        // Given
        fakeSyncService.switchInventoryResult =
            Result.success(InventoryInfoDto(id = "inv-2", name = "Büro", isActive = true))
        fakeSyncService.listInventoriesResult = Result.success(
            listOf(
                InventoryInfoDto(id = "inv-1", name = "Keller", isActive = false),
                InventoryInfoDto(id = "inv-2", name = "Büro", isActive = true)
            )
        )
        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.switchInventory("inv-2")
        advanceUntilIdle()

        // Then
        assertEquals("Büro", viewModel.uiState.value.activeInventoryName)
        assertEquals("inv-2", fakeSettingsRepository.store[SettingsKeys.ACTIVE_INVENTORY_ID])
        assertFalse(viewModel.uiState.value.isSwitching)
    }

    @Test
    fun test_switchInventory_sameInventory_noOp() = runTest(testDispatcher) {
        // Given
        fakeSyncService.listInventoriesResult = Result.success(
            listOf(InventoryInfoDto(id = "inv-1", name = "Keller", isActive = true))
        )
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.loadInventories()
        advanceUntilIdle()

        // When
        viewModel.switchInventory("inv-1")
        advanceUntilIdle()

        // Then – no switch was attempted
        assertFalse(viewModel.uiState.value.isSwitching)
    }

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

        // When
        viewModel.showCreateDialog()

        // Then
        assertTrue(viewModel.uiState.value.isCreateDialogVisible)

        // When
        viewModel.dismissCreateDialog()

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

    @Test
    fun test_dismissError_clearsError() = runTest(testDispatcher) {
        // Given
        fakeSyncService.listInventoriesResult = Result.failure(SyncError.ConnectionError(Exception("offline")))
        val viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.loadInventories()
        advanceUntilIdle()

        // When
        viewModel.dismissError()

        // Then
        assertNull(viewModel.uiState.value.error)
    }
}

// region Fakes

private class FakeSettingsRepository : SettingsRepository {
    val store = mutableMapOf<String, String>()

    override suspend fun getValue(key: String): String? = store[key]

    override suspend fun setValue(key: String, value: String) {
        store[key] = value
    }

    override fun observeValue(key: String): Flow<String?> =
        MutableStateFlow(store[key])

    override fun getAll(): Flow<List<SettingsEntity>> =
        MutableStateFlow(store.map { SettingsEntity(key = it.key, value = it.value) })

    override suspend fun getString(key: SettingsKey.StringKey): String =
        getValue(key.key) ?: key.defaultValue

    override suspend fun getStringOrNull(key: SettingsKey.StringKey): String? =
        getValue(key.key)

    override suspend fun setString(key: SettingsKey.StringKey, value: String) =
        setValue(key.key, value)

    override fun observeString(key: SettingsKey.StringKey): Flow<String> =
        MutableStateFlow(store[key.key] ?: key.defaultValue)
}

private class FakeSyncService : SyncService {
    var listInventoriesResult: Result<List<InventoryInfoDto>> = Result.success(emptyList())
    var createInventoryResult: Result<InventoryInfoDto> =
        Result.success(InventoryInfoDto(id = "new-inv", name = "New", isActive = true))
    var switchInventoryResult: Result<InventoryInfoDto> =
        Result.success(InventoryInfoDto(id = "switched", name = "Switched", isActive = true))

    override suspend fun downloadInventory(since: Long?): Result<InventoryDto> =
        Result.success(InventoryDto(categories = emptyList(), locations = emptyList(), items = emptyList(), settings = emptyList()))

    override suspend fun uploadInventory(inventory: InventoryDto): Result<InventoryDto> =
        Result.success(inventory)

    override suspend fun login(serverUrl: String, username: String, password: String): Result<Unit> =
        Result.success(Unit)

    override suspend fun logout() {}

    override suspend fun patchItem(itemId: String, item: ItemDto): Result<Unit> =
        Result.success(Unit)

    override suspend fun deleteItem(itemId: String): Result<Unit> =
        Result.success(Unit)

    override suspend fun listInventories(): Result<List<InventoryInfoDto>> = listInventoriesResult

    override suspend fun createInventory(name: String): Result<InventoryInfoDto> = createInventoryResult

    override suspend fun switchInventory(inventoryId: String): Result<InventoryInfoDto> = switchInventoryResult
}

private class FakeImportExportRepository : ImportExportRepository {
    override suspend fun exportToJson(): String = "{}"
    override suspend fun importFromJson(json: String): Result<Unit> = Result.success(Unit)
    override suspend fun exportToMarkdown(): String = ""
    override suspend fun exportToCsv(): String = ""
    override suspend fun exportToPdf(file: java.io.File) {}
    override suspend fun exportToInventoryDto(): InventoryDto =
        InventoryDto(categories = emptyList(), locations = emptyList(), items = emptyList(), settings = emptyList())
    override suspend fun importFromInventoryDto(dto: InventoryDto): Result<Unit> = Result.success(Unit)
}

// endregion
