package de.softwareapp_hb.privateqrscanner.ui

import de.softwareapp_hb.privateqrscanner.domain.ScanResult
import de.softwareapp_hb.privateqrscanner.testutil.MainDispatcherRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test

@OptIn(ExperimentalCoroutinesApi::class)
class ScannerViewModelTest {

    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    @Test
    fun onScan_firstSingleScan_updatesStateAndSaves() = runTest {
        val saved = mutableListOf<Pair<String, String>>()
        var now = 1_000L
        val viewModel = ScannerViewModel(
            saveScan = { content, type -> saved += content to type },
            nowProvider = { now }
        )

        viewModel.onScan(ScanResult(content = "https://example.com", type = "URL"))
        advanceUntilIdle()

        val state = viewModel.uiState.value
        assertEquals("https://example.com", state.lastResult?.content)
        assertEquals(false, state.analysisEnabled)
        assertEquals(1, state.scanFeedbackNonce)
        assertEquals(0, state.duplicateFeedbackNonce)
        assertEquals(listOf("URL|https://example.com"), state.recentScanKeys)
        assertEquals(listOf("https://example.com" to "URL"), saved)
    }

    @Test
    fun duplicateScan_afterResume_incrementsDuplicateFeedback() = runTest {
        val saved = mutableListOf<Pair<String, String>>()
        var now = 1_000L
        val viewModel = ScannerViewModel(
            saveScan = { content, type -> saved += content to type },
            nowProvider = { now }
        )

        val result = ScanResult(content = "ABC", type = "Text")
        viewModel.onScan(result)
        viewModel.resumeScanning()

        now = 4_000L
        viewModel.onScan(result)
        advanceUntilIdle()

        val state = viewModel.uiState.value
        assertEquals(2, state.scanFeedbackNonce)
        assertEquals(1, state.duplicateFeedbackNonce)
        assertEquals(2, saved.size)
    }

    @Test
    fun batchMode_addsUniqueKeepsNewestFirst_andClearWorks() = runTest {
        val saved = mutableListOf<Pair<String, String>>()
        var now = 1_000L
        val viewModel = ScannerViewModel(
            saveScan = { content, type -> saved += content to type },
            nowProvider = { now }
        )

        viewModel.setBatchMode(true)
        viewModel.onScan(ScanResult(content = "A", type = "Text"))

        now = 2_000L
        viewModel.onScan(ScanResult(content = "B", type = "Text"))

        now = 3_600L
        viewModel.onScan(ScanResult(content = "A", type = "Text"))
        advanceUntilIdle()

        val state = viewModel.uiState.value
        assertEquals(2, state.batchResults.size)
        assertEquals("B", state.batchResults[0].result.content)
        assertEquals(2_000L, state.batchResults[0].timestamp)
        assertEquals("A", state.batchResults[1].result.content)
        assertEquals(1, state.duplicateFeedbackNonce)
        assertEquals(3, state.scanFeedbackNonce)
        assertTrue(state.analysisEnabled)

        viewModel.clearBatchResults()
        assertTrue(viewModel.uiState.value.batchResults.isEmpty())
        assertEquals(3, saved.size)
    }

    @Test
    fun sameCodeWithinHoldoff_isIgnored() = runTest {
        val saved = mutableListOf<Pair<String, String>>()
        var now = 1_000L
        val viewModel = ScannerViewModel(
            saveScan = { content, type -> saved += content to type },
            nowProvider = { now }
        )

        val result = ScanResult(content = "HOLD", type = "Text")
        viewModel.setBatchMode(true)
        viewModel.onScan(result)

        now = 2_000L
        viewModel.onScan(result)
        advanceUntilIdle()

        val state = viewModel.uiState.value
        assertEquals(1, state.scanFeedbackNonce)
        assertEquals(1, saved.size)
        assertEquals(1, state.batchResults.size)
    }

    @Test
    fun onScan_base64EncodedScan_savesTypeWithBase64Marker() = runTest {
        val saved = mutableListOf<Pair<String, String>>()
        val viewModel = ScannerViewModel(
            saveScan = { content, type -> saved += content to type },
            nowProvider = { 1_000L }
        )

        viewModel.onScan(ScanResult(content = "AAEC", type = "Text", isBase64Encoded = true))
        advanceUntilIdle()

        val state = viewModel.uiState.value
        assertEquals("Text (Base64)", state.lastResult?.displayType)
        assertEquals(listOf("Text (Base64)|AAEC"), state.recentScanKeys)
        assertEquals(listOf("AAEC" to "Text (Base64)"), saved)
    }
}
