package de.krisenvorrat.app.ui.settings

import android.content.Context
import androidx.core.content.FileProvider
import android.content.ContentResolver
import android.net.Uri
import de.krisenvorrat.app.data.sync.WebSocketClient
import de.krisenvorrat.app.data.sync.WebSocketEvent
import de.krisenvorrat.app.data.db.entity.SettingsEntity
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.app.domain.model.AgeGroup
import de.krisenvorrat.app.domain.model.AgeGroupEntry
import de.krisenvorrat.app.domain.model.defaultAgeGroups
import de.krisenvorrat.app.domain.model.toJson
import de.krisenvorrat.shared.model.InventoryDto
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 io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.File

@OptIn(ExperimentalCoroutinesApi::class)
class SettingsViewModelTest {

    private val testDispatcher = StandardTestDispatcher()
    private lateinit var fakeSettingsRepository: FakeSettingsRepository
    private lateinit var fakeImportExportRepository: FakeImportExportRepository
    private lateinit var fakeSyncService: FakeSyncService
    private lateinit var fakeWebSocketClient: FakeWebSocketClient
    private lateinit var mockContext: Context
    private lateinit var tempDir: File
    private lateinit var viewModel: SettingsViewModel

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        fakeSettingsRepository = FakeSettingsRepository()
        fakeImportExportRepository = FakeImportExportRepository()
        fakeSyncService = FakeSyncService()
        fakeWebSocketClient = FakeWebSocketClient()
        tempDir = File(System.getProperty("java.io.tmpdir"), "test_exports")
        tempDir.mkdirs()
        mockContext = mockk(relaxed = true) {
            every { cacheDir } returns tempDir
            every { packageName } returns "de.krisenvorrat.app"
        }
    }

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

    private fun createViewModel() = SettingsViewModel(
        settingsRepository = fakeSettingsRepository,
        importExportRepository = fakeImportExportRepository,
        syncService = fakeSyncService,
        webSocketClient = fakeWebSocketClient,
        context = mockContext
    )

    @Test
    fun test_init_withNoStoredValues_showsDefaultAgeGroups() = runTest(testDispatcher) {
        // Given – no stored settings
        viewModel = createViewModel()

        // When
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isLoading)
        assertEquals(defaultAgeGroups(), state.ageGroups)
    }

    @Test
    fun test_init_withStoredAgeGroups_showsStoredGroups() = runTest(testDispatcher) {
        // Given
        val stored = defaultAgeGroups().map { entry ->
            if (entry.ageGroup == AgeGroup.ERWACHSENER) entry.copy(count = 3, kcalPerDay = 2500)
            else entry
        }
        fakeSettingsRepository.store[SettingsViewModel.KEY_AGE_GROUPS] = stored.toJson()
        viewModel = createViewModel()

        // When
        advanceUntilIdle()

        // Then
        val erwachsener = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.ERWACHSENER }
        assertEquals(3, erwachsener.count)
        assertEquals(2500, erwachsener.kcalPerDay)
    }

    @Test
    fun test_init_withLegacyHouseholdSize_migratesErwachsener() = runTest(testDispatcher) {
        // Given – legacy keys present, no age_groups key
        fakeSettingsRepository.store[SettingsViewModel.KEY_HOUSEHOLD_SIZE] = "3"
        fakeSettingsRepository.store[SettingsViewModel.KEY_DAILY_KCAL_PER_PERSON] = "2500"
        viewModel = createViewModel()

        // When
        advanceUntilIdle()

        // Then
        val erwachsener = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.ERWACHSENER }
        assertEquals(3, erwachsener.count)
        assertEquals(2500, erwachsener.kcalPerDay)
    }

    @Test
    fun test_init_withLegacyHouseholdSizeOnly_migratesWithDefaultKcal() = runTest(testDispatcher) {
        // Given – nur KEY_HOUSEHOLD_SIZE gesetzt, kein KEY_DAILY_KCAL_PER_PERSON
        fakeSettingsRepository.store[SettingsViewModel.KEY_HOUSEHOLD_SIZE] = "3"
        viewModel = createViewModel()

        // When
        advanceUntilIdle()

        // Then
        val erwachsener = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.ERWACHSENER }
        assertEquals(3, erwachsener.count)
        assertEquals(AgeGroup.ERWACHSENER.defaultKcal, erwachsener.kcalPerDay)
    }

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

        // When
        viewModel.onAgeGroupCountChanged(AgeGroup.KLEINKIND, "2")

        // Then
        val kleinkind = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.KLEINKIND }
        assertEquals(2, kleinkind.count)
        assertFalse(viewModel.uiState.value.isSaved)
    }

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

        // When
        viewModel.onAgeGroupKcalChanged(AgeGroup.ERWACHSENER, "2200")

        // Then
        val erwachsener = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.ERWACHSENER }
        assertEquals(2200, erwachsener.kcalPerDay)
        assertFalse(viewModel.uiState.value.isSaved)
    }

    @Test
    fun test_onAgeGroupCountChanged_withNegativeValue_doesNotUpdateState() = runTest(testDispatcher) {
        // Given
        viewModel = createViewModel()
        advanceUntilIdle()
        val initialGroups = viewModel.uiState.value.ageGroups

        // When
        viewModel.onAgeGroupCountChanged(AgeGroup.KLEINKIND, "-1")

        // Then
        assertEquals(initialGroups, viewModel.uiState.value.ageGroups)
    }

    @Test
    fun test_onAgeGroupKcalChanged_withZeroValue_doesNotUpdateState() = runTest(testDispatcher) {
        // Given
        viewModel = createViewModel()
        advanceUntilIdle()
        val initialGroups = viewModel.uiState.value.ageGroups

        // When
        viewModel.onAgeGroupKcalChanged(AgeGroup.ERWACHSENER, "0")

        // Then
        assertEquals(initialGroups, viewModel.uiState.value.ageGroups)
    }

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

        // When
        viewModel.saveSettings()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.isSaved)
        assertTrue(fakeSettingsRepository.store.containsKey(SettingsViewModel.KEY_AGE_GROUPS))
    }

    @Test
    fun test_exportMarkdown_success_emitsShareContent() = runTest(testDispatcher) {
        // Given
        fakeImportExportRepository.markdownResult = "# Krisenvorrat Inventar\n"
        viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.exportMarkdown()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isExporting)
        assertNotNull(state.shareContent)
        assertTrue(state.shareContent is ShareContent.Markdown)
        assertEquals("# Krisenvorrat Inventar\n", (state.shareContent as ShareContent.Markdown).text)
    }

    @Test
    fun test_exportMarkdown_emptyDatabase_emitsMarkdownWithHint() = runTest(testDispatcher) {
        // Given
        fakeImportExportRepository.markdownResult = "# Krisenvorrat Inventar\n\nKeine Artikel vorhanden\n"
        viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.exportMarkdown()
        advanceUntilIdle()

        // Then
        val content = viewModel.uiState.value.shareContent as ShareContent.Markdown
        assertTrue(content.text.contains("Keine Artikel vorhanden"))
    }

    @Test
    fun test_exportJson_success_emitsShareContentWithUri() = runTest(testDispatcher) {
        // Given
        fakeImportExportRepository.jsonResult = """{"categories":[],"locations":[],"items":[],"settings":[]}"""
        mockkStatic(FileProvider::class)
        val mockUri = mockk<android.net.Uri>()
        every { FileProvider.getUriForFile(any(), any(), any()) } returns mockUri
        viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.exportJson()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isExporting)
        assertNotNull(state.shareContent)
        assertTrue(state.shareContent is ShareContent.Json)
        assertEquals(mockUri, (state.shareContent as ShareContent.Json).fileUri)
        unmockkStatic(FileProvider::class)
    }

    @Test
    fun test_exportJson_failure_setsExportError() = runTest(testDispatcher) {
        // Given
        fakeImportExportRepository.shouldThrow = true
        viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.exportJson()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isExporting)
        assertNull(state.shareContent)
        assertEquals("Export fehlgeschlagen", state.exportError)
    }

    @Test
    fun test_exportMarkdown_failure_setsExportError() = runTest(testDispatcher) {
        // Given
        fakeImportExportRepository.shouldThrow = true
        viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.exportMarkdown()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isExporting)
        assertNull(state.shareContent)
        assertEquals("Export fehlgeschlagen", state.exportError)
    }

    @Test
    fun test_onShareHandled_clearsShareContent() = runTest(testDispatcher) {
        // Given
        fakeImportExportRepository.markdownResult = "# Test"
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.exportMarkdown()
        advanceUntilIdle()

        // When
        viewModel.onShareHandled()

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

    @Test
    fun test_onExportErrorDismissed_clearsError() = runTest(testDispatcher) {
        // Given
        fakeImportExportRepository.shouldThrow = true
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.exportJson()
        advanceUntilIdle()

        // When
        viewModel.onExportErrorDismissed()

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

    @Test
    fun test_onImportFileSelected_setsPendingUri() = runTest(testDispatcher) {
        // Given
        viewModel = createViewModel()
        advanceUntilIdle()
        val mockUri = mockk<Uri>()

        // When
        viewModel.onImportFileSelected(mockUri)

        // Then
        assertEquals(mockUri, viewModel.uiState.value.pendingImportUri)
    }

    @Test
    fun test_onImportDismissed_clearsPendingUri() = runTest(testDispatcher) {
        // Given
        viewModel = createViewModel()
        advanceUntilIdle()
        val mockUri = mockk<Uri>()
        viewModel.onImportFileSelected(mockUri)

        // When
        viewModel.onImportDismissed()

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

    @Test
    fun test_onImportConfirmed_success_setsSuccessResult() = runTest(testDispatcher) {
        // Given
        val mockUri = mockk<Uri>()
        val mockContentResolver = mockk<ContentResolver>()
        val jsonContent = """{"version":1,"categories":[],"locations":[],"items":[],"settings":[]}"""
        every { mockContext.contentResolver } returns mockContentResolver
        every { mockContentResolver.openInputStream(mockUri) } returns ByteArrayInputStream(jsonContent.toByteArray())
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.onImportFileSelected(mockUri)

        // When
        viewModel.onImportConfirmed()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isImporting)
        assertNull(state.pendingImportUri)
        assertTrue(state.importResult is ImportResult.Success)
    }

    @Test
    fun test_onImportConfirmed_invalidJson_setsErrorResult() = runTest(testDispatcher) {
        // Given
        val mockUri = mockk<Uri>()
        val mockContentResolver = mockk<ContentResolver>()
        every { mockContext.contentResolver } returns mockContentResolver
        every { mockContentResolver.openInputStream(mockUri) } returns ByteArrayInputStream("{ not valid }".toByteArray())
        fakeImportExportRepository.importShouldFail = true
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.onImportFileSelected(mockUri)

        // When
        viewModel.onImportConfirmed()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isImporting)
        assertTrue(state.importResult is ImportResult.Error)
    }

    @Test
    fun test_onImportConfirmed_emptyFile_setsErrorResult() = runTest(testDispatcher) {
        // Given
        val mockUri = mockk<Uri>()
        val mockContentResolver = mockk<ContentResolver>()
        every { mockContext.contentResolver } returns mockContentResolver
        every { mockContentResolver.openInputStream(mockUri) } returns ByteArrayInputStream(ByteArray(0))
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.onImportFileSelected(mockUri)

        // When
        viewModel.onImportConfirmed()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isImporting)
        assertTrue(state.importResult is ImportResult.Error)
        assertEquals("Datei ist leer oder konnte nicht gelesen werden", (state.importResult as ImportResult.Error).message)
    }

    @Test
    fun test_onImportConfirmed_nullInputStream_setsErrorResult() = runTest(testDispatcher) {
        // Given
        val mockUri = mockk<Uri>()
        val mockContentResolver = mockk<ContentResolver>()
        every { mockContext.contentResolver } returns mockContentResolver
        every { mockContentResolver.openInputStream(mockUri) } returns null
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.onImportFileSelected(mockUri)

        // When
        viewModel.onImportConfirmed()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isImporting)
        assertTrue(state.importResult is ImportResult.Error)
    }

    @Test
    fun test_onImportResultDismissed_clearsImportResult() = runTest(testDispatcher) {
        // Given
        val mockUri = mockk<Uri>()
        val mockContentResolver = mockk<ContentResolver>()
        val jsonContent = """{"version":1,"categories":[],"locations":[],"items":[],"settings":[]}"""
        every { mockContext.contentResolver } returns mockContentResolver
        every { mockContentResolver.openInputStream(mockUri) } returns ByteArrayInputStream(jsonContent.toByteArray())
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.onImportFileSelected(mockUri)
        viewModel.onImportConfirmed()
        advanceUntilIdle()

        // When
        viewModel.onImportResultDismissed()

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

    // --- Sync Tests ---

    @Test
    fun test_init_withStoredSyncSettings_loadsSyncFields() = runTest(testDispatcher) {
        // Given
        fakeSettingsRepository.store[SettingsViewModel.KEY_SERVER_URL] = "https://example.com"
        fakeSettingsRepository.store[SettingsViewModel.KEY_AUTH_USERNAME] = "testuser"
        fakeSettingsRepository.store[SettingsViewModel.KEY_ACCESS_TOKEN] = "access-token-123"
        fakeSettingsRepository.store[SettingsViewModel.KEY_SYNC_LAST_TIMESTAMP] = "1715700000000"

        viewModel = createViewModel()

        // When
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertEquals("https://example.com", state.serverUrl)
        assertTrue(state.isLoggedIn)
        assertEquals("testuser", state.loggedInUsername)
        assertNotNull(state.lastSyncTime)
    }

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

        // When
        viewModel.onServerUrlChanged("https://new-server.com")

        // Then
        assertEquals("https://new-server.com", viewModel.uiState.value.serverUrl)
        assertFalse(viewModel.uiState.value.isSaved)
    }

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

        // When
        viewModel.onLoginUsernameChanged("newuser")

        // Then
        assertEquals("newuser", viewModel.uiState.value.loginUsername)
    }

    @Test
    fun test_login_success_setsLoggedIn() = runTest(testDispatcher) {
        // Given
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.onServerUrlChanged("https://server.com")
        viewModel.onLoginUsernameChanged("admin")
        viewModel.onLoginPasswordChanged("secret")

        // When
        viewModel.login()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.isLoggedIn)
        assertEquals("admin", state.loggedInUsername)
        assertFalse(state.isLoggingIn)
        assertNull(state.loginError)
    }

    @Test
    fun test_login_failure_setsLoginError() = runTest(testDispatcher) {
        // Given
        fakeSyncService.loginShouldFail = true
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.onServerUrlChanged("https://server.com")
        viewModel.onLoginUsernameChanged("admin")
        viewModel.onLoginPasswordChanged("wrong")

        // When
        viewModel.login()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isLoggedIn)
        assertNotNull(state.loginError)
    }

    @Test
    fun test_logout_clearsLoginState() = runTest(testDispatcher) {
        // Given
        fakeSettingsRepository.store[SettingsViewModel.KEY_AUTH_USERNAME] = "testuser"
        fakeSettingsRepository.store[SettingsViewModel.KEY_ACCESS_TOKEN] = "token-123"
        fakeSettingsRepository.store[SettingsViewModel.KEY_SERVER_URL] = "https://server.com"
        viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.logout()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertFalse(state.isLoggedIn)
        assertEquals("", state.loggedInUsername)
    }

    @Test
    fun test_saveSettings_persistsServerUrl() = runTest(testDispatcher) {
        // Given
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.onServerUrlChanged("https://myserver.com")

        // When
        viewModel.saveSettings()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.isSaved)
        assertEquals("https://myserver.com", fakeSettingsRepository.store[SettingsViewModel.KEY_SERVER_URL])
    }

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

        // When
        viewModel.pushSync()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.syncStatus is SyncStatus.Success)
        assertNotNull(state.lastSyncTime)
        assertNotNull(fakeSettingsRepository.store[SettingsViewModel.KEY_SYNC_LAST_TIMESTAMP])
    }

    @Test
    fun test_pushSync_failure_setsSyncStatusError() = runTest(testDispatcher) {
        // Given
        fakeSyncService.uploadShouldFail = true
        fakeSyncService.uploadError = SyncError.ConnectionError()
        viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.pushSync()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.syncStatus is SyncStatus.Error)
        assertTrue((state.syncStatus as SyncStatus.Error).message.contains("Server nicht erreichbar"))
    }

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

        // When
        viewModel.pullSync()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.syncStatus is SyncStatus.Success)
        assertNotNull(state.lastSyncTime)
        assertNotNull(fakeSettingsRepository.store[SettingsViewModel.KEY_SYNC_LAST_TIMESTAMP])
    }

    @Test
    fun test_pullSync_authError_setsSyncStatusError() = runTest(testDispatcher) {
        // Given
        fakeSyncService.downloadShouldFail = true
        fakeSyncService.downloadError = de.krisenvorrat.app.domain.model.SyncError.AuthError()
        viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.pullSync()
        advanceUntilIdle()

        // Then
        val state = viewModel.uiState.value
        assertTrue(state.syncStatus is SyncStatus.Error)
        assertTrue((state.syncStatus as SyncStatus.Error).message.contains("Authentifizierung"))
    }

    @Test
    fun test_onSyncStatusDismissed_resetsSyncStatus() = runTest(testDispatcher) {
        // Given
        viewModel = createViewModel()
        advanceUntilIdle()
        viewModel.pushSync()
        advanceUntilIdle()

        // When
        viewModel.onSyncStatusDismissed()

        // Then
        assertTrue(viewModel.uiState.value.syncStatus is SyncStatus.Idle)
    }
}

// region Test Helpers

private class FakeSettingsRepository : SettingsRepository {
    val store = mutableMapOf<String, String>()
    private val allFlow = MutableStateFlow<List<SettingsEntity>>(emptyList())

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

    override suspend fun setValue(key: String, value: String) {
        store[key] = value
        allFlow.value = store.map { SettingsEntity(key = it.key, value = it.value) }
    }

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

    override fun getAll(): Flow<List<SettingsEntity>> = allFlow
}

private class FakeImportExportRepository : ImportExportRepository {
    var jsonResult = """{"version":1,"categories":[],"locations":[],"items":[],"settings":[]}"""
    var markdownResult = "# Krisenvorrat Inventar\n"
    var shouldThrow = false
    var importShouldFail = false
    var inventoryDto = InventoryDto(
        categories = emptyList(),
        locations = emptyList(),
        items = emptyList(),
        settings = emptyList()
    )

    override suspend fun exportToJson(): String {
        if (shouldThrow) throw RuntimeException("Test error")
        return jsonResult
    }

    override suspend fun importFromJson(json: String): Result<Unit> {
        if (shouldThrow || importShouldFail) return Result.failure(RuntimeException("Test error"))
        return Result.success(Unit)
    }

    override suspend fun exportToMarkdown(): String {
        if (shouldThrow) throw RuntimeException("Test error")
        return markdownResult
    }

    override suspend fun exportToInventoryDto(): InventoryDto {
        if (shouldThrow) throw RuntimeException("Test error")
        return inventoryDto
    }

    override suspend fun importFromInventoryDto(dto: InventoryDto): Result<Unit> {
        if (shouldThrow || importShouldFail) return Result.failure(RuntimeException("Test error"))
        return Result.success(Unit)
    }
}

private class FakeSyncService : SyncService {
    var uploadShouldFail = false
    var downloadShouldFail = false
    var loginShouldFail = false
    var uploadError: Exception = RuntimeException("Upload failed")
    var downloadError: Exception = RuntimeException("Download failed")
    var downloadResult = InventoryDto(
        categories = emptyList(),
        locations = emptyList(),
        items = emptyList(),
        settings = emptyList()
    )
    var uploadResult = InventoryDto(
        categories = emptyList(),
        locations = emptyList(),
        items = emptyList(),
        settings = emptyList()
    )

    override suspend fun downloadInventory(since: Long?): Result<InventoryDto> {
        if (downloadShouldFail) return Result.failure(downloadError)
        return Result.success(downloadResult)
    }

    override suspend fun uploadInventory(inventory: InventoryDto): Result<InventoryDto> {
        if (uploadShouldFail) return Result.failure(uploadError)
        return Result.success(uploadResult)
    }

    override suspend fun login(serverUrl: String, username: String, password: String): Result<Unit> {
        if (loginShouldFail) return Result.failure(de.krisenvorrat.app.domain.model.SyncError.AuthError())
        return Result.success(Unit)
    }

    override suspend fun logout() {}
    override suspend fun patchItem(itemId: String, item: de.krisenvorrat.shared.model.ItemDto): Result<Unit> = Result.success(Unit)
    override suspend fun deleteItem(itemId: String): Result<Unit> = Result.success(Unit)
}

private class FakeWebSocketClient : WebSocketClient {
    private val _events = kotlinx.coroutines.flow.MutableSharedFlow<WebSocketEvent>(extraBufferCapacity = 16)
    override val events: kotlinx.coroutines.flow.SharedFlow<WebSocketEvent> = _events
    var connectedUrl: String? = null
    override fun connect(serverUrl: String, accessToken: String) { connectedUrl = serverUrl }
    override fun disconnect() { connectedUrl = null }
}

// endregion
