package de.krisenvorrat.app.ui.update

import de.krisenvorrat.app.domain.model.UpdateCheckResult
import de.krisenvorrat.app.domain.model.VersionInfo
import de.krisenvorrat.app.domain.repository.UpdateRepository
import de.krisenvorrat.app.domain.usecase.ApkInstaller
import de.krisenvorrat.app.domain.usecase.CheckForUpdateUseCase
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
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.assertTrue
import org.junit.Before
import org.junit.Test
import java.io.File

@OptIn(ExperimentalCoroutinesApi::class)
class UpdateViewModelTest {

    private val testDispatcher = StandardTestDispatcher()
    private val checkForUpdateUseCase: CheckForUpdateUseCase = mockk()
    private val updateRepository: UpdateRepository = mockk()
    private val apkInstaller: ApkInstaller = mockk(relaxed = true)
    private val context: android.content.Context = mockk(relaxed = true)

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        val cacheDir = File(System.getProperty("java.io.tmpdir"), "test-cache-${System.nanoTime()}")
        cacheDir.mkdirs()
        every { context.cacheDir } returns cacheDir
    }

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

    private fun createViewModel(): UpdateViewModel {
        return UpdateViewModel(
            checkForUpdateUseCase = checkForUpdateUseCase,
            updateRepository = updateRepository,
            apkInstaller = apkInstaller,
            context = context
        )
    }

    @Test
    fun test_init_updateAvailable_showsAvailableStatus() = runTest(testDispatcher) {
        // Given
        val versionInfo = VersionInfo(versionCode = 10, versionName = "2.0", apkUrl = "https://example.com/app.apk")
        coEvery { checkForUpdateUseCase(any()) } returns UpdateCheckResult.UpdateAvailable(versionInfo)

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

        // Then
        val status = viewModel.uiState.value.status
        assertTrue(status is UpdateStatus.Available)
        assertEquals("2.0", (status as UpdateStatus.Available).versionName)
        assertEquals("https://example.com/app.apk", status.apkUrl)
    }

    @Test
    fun test_init_upToDate_showsHiddenStatus() = runTest(testDispatcher) {
        // Given
        coEvery { checkForUpdateUseCase(any()) } returns UpdateCheckResult.UpToDate

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

        // Then
        assertTrue(viewModel.uiState.value.status is UpdateStatus.Hidden)
    }

    @Test
    fun test_init_notConfigured_showsHiddenStatus() = runTest(testDispatcher) {
        // Given
        coEvery { checkForUpdateUseCase(any()) } returns UpdateCheckResult.NotConfigured

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

        // Then
        assertTrue(viewModel.uiState.value.status is UpdateStatus.Hidden)
    }

    @Test
    fun test_init_checkError_showsHiddenStatus() = runTest(testDispatcher) {
        // Given
        coEvery { checkForUpdateUseCase(any()) } returns UpdateCheckResult.Error(Exception("Network error"))

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

        // Then
        assertTrue(viewModel.uiState.value.status is UpdateStatus.Hidden)
    }

    @Test
    fun test_startDownload_downloadSucceeds_showsReadyToInstallAndCallsInstaller() = runTest(testDispatcher) {
        // Given
        val versionInfo = VersionInfo(versionCode = 10, versionName = "2.0", apkUrl = "https://example.com/app.apk")
        coEvery { checkForUpdateUseCase(any()) } returns UpdateCheckResult.UpdateAvailable(versionInfo)

        val targetFileSlot = slot<File>()
        coEvery {
            updateRepository.downloadApk(
                apkUrl = eq("https://example.com/app.apk"),
                targetFile = capture(targetFileSlot),
                onProgress = any()
            )
        } coAnswers {
            val onProgress = thirdArg<(Float) -> Unit>()
            onProgress(0.5f)
            onProgress(1.0f)
            targetFileSlot.captured.parentFile?.mkdirs()
            targetFileSlot.captured.createNewFile()
            Result.success(targetFileSlot.captured)
        }

        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.startDownload()
        advanceUntilIdle()

        // Then
        coVerify { updateRepository.downloadApk(any(), any(), any()) }
        verify { apkInstaller.install(any()) }
        val status = viewModel.uiState.value.status
        assertTrue(status is UpdateStatus.ReadyToInstall)
        assertEquals("2.0", (status as UpdateStatus.ReadyToInstall).versionName)
    }

    @Test
    fun test_startDownload_downloadFails_showsError() = runTest(testDispatcher) {
        // Given
        val versionInfo = VersionInfo(versionCode = 10, versionName = "2.0", apkUrl = "https://example.com/app.apk")
        coEvery { checkForUpdateUseCase(any()) } returns UpdateCheckResult.UpdateAvailable(versionInfo)

        coEvery {
            updateRepository.downloadApk(any(), any(), any())
        } returns Result.failure(Exception("Download failed"))

        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.startDownload()
        advanceUntilIdle()

        // Then
        val status = viewModel.uiState.value.status
        assertTrue(status is UpdateStatus.Error)
        assertEquals("Download failed", (status as UpdateStatus.Error).message)
    }

    @Test
    fun test_dismiss_hidesStatus() = runTest(testDispatcher) {
        // Given
        val versionInfo = VersionInfo(versionCode = 10, versionName = "2.0", apkUrl = "https://example.com/app.apk")
        coEvery { checkForUpdateUseCase(any()) } returns UpdateCheckResult.UpdateAvailable(versionInfo)

        val viewModel = createViewModel()
        advanceUntilIdle()
        assertTrue(viewModel.uiState.value.status is UpdateStatus.Available)

        // When
        viewModel.dismiss()

        // Then
        assertTrue(viewModel.uiState.value.status is UpdateStatus.Hidden)
    }

    @Test
    fun test_startDownload_reportsProgress() = runTest(testDispatcher) {
        // Given
        val versionInfo = VersionInfo(versionCode = 10, versionName = "2.0", apkUrl = "https://example.com/app.apk")
        coEvery { checkForUpdateUseCase(any()) } returns UpdateCheckResult.UpdateAvailable(versionInfo)

        val progressValues = mutableListOf<Float>()
        val targetFileSlot = slot<File>()

        coEvery {
            updateRepository.downloadApk(any(), capture(targetFileSlot), any())
        } coAnswers {
            val onProgress = thirdArg<(Float) -> Unit>()
            onProgress(0.25f)
            progressValues.add(0.25f)
            onProgress(0.75f)
            progressValues.add(0.75f)
            onProgress(1.0f)
            progressValues.add(1.0f)
            targetFileSlot.captured.parentFile?.mkdirs()
            targetFileSlot.captured.createNewFile()
            Result.success(targetFileSlot.captured)
        }

        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.startDownload()
        advanceUntilIdle()

        // Then
        assertEquals(3, progressValues.size)
        assertEquals(0.25f, progressValues[0], 0.001f)
        assertEquals(0.75f, progressValues[1], 0.001f)
        assertEquals(1.0f, progressValues[2], 0.001f)
    }

    @Test
    fun test_startDownload_whenNotAvailable_doesNothing() = runTest(testDispatcher) {
        // Given
        coEvery { checkForUpdateUseCase(any()) } returns UpdateCheckResult.UpToDate

        val viewModel = createViewModel()
        advanceUntilIdle()

        // When
        viewModel.startDownload()
        advanceUntilIdle()

        // Then
        assertTrue(viewModel.uiState.value.status is UpdateStatus.Hidden)
        coVerify(exactly = 0) { updateRepository.downloadApk(any(), any(), any()) }
    }
}
