package lu.rewardflow.terminal.ui.pin

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import lu.rewardflow.terminal.data.db.dao.PendingTransactionDao
import lu.rewardflow.terminal.data.model.PinForDeviceItem
import lu.rewardflow.terminal.data.network.NetworkMonitor
import lu.rewardflow.terminal.data.repository.StaffPinRepository
import javax.inject.Inject

/**
 * Powers the PIN-entry screen.
 *
 * The screen always renders the cached staff list (so it works offline)
 * and a numeric keypad. As the user types, [enteredDigits] grows; once
 * it hits the configured length the view-model bcrypt-verifies against
 * the cached hashes and emits a [VerifiedPin] which the screen consumes
 * to navigate.
 *
 * Lockout state from the server (``is_locked`` on the cached row) is
 * honored — locked PINs simply never match. Reporting failed attempts
 * BACK to the server's lockout counter is deferred to a follow-up.
 */
@HiltViewModel
class PinViewModel @Inject constructor(
    private val staffPinRepository: StaffPinRepository,
    private val pendingTransactionDao: PendingTransactionDao,
    networkMonitor: NetworkMonitor,
) : ViewModel() {

    private val _state = MutableStateFlow(PinUiState())
    val state: StateFlow<PinUiState> = _state.asStateFlow()

    init {
        loadPins()

        // Wire pending-sync count + online badge into the state flow so the
        // view doesn't have to subscribe to multiple flows itself.
        combine(
            pendingTransactionDao.getPendingCount(),
            networkMonitor.isOnline,
        ) { pending, online -> pending to online }
            .onEach { (pending, online) ->
                _state.value = _state.value.copy(
                    pendingSyncCount = pending,
                    isOnline = online,
                )
            }
            .launchIn(viewModelScope)
    }

    fun loadPins() {
        viewModelScope.launch {
            _state.value = _state.value.copy(loading = true, errorMessage = null)
            // Cached if available; if empty, attempt a refresh.
            val pins = runCatching { staffPinRepository.listOrRefresh() }
                .getOrDefault(emptyList())
            _state.value = _state.value.copy(loading = false, pins = pins)
        }
    }

    fun onDigit(digit: Char) {
        if (!digit.isDigit()) return
        val current = _state.value
        if (current.verifying || current.verified != null) return
        val next = (current.enteredDigits + digit).take(MAX_PIN_LENGTH)
        _state.value = current.copy(enteredDigits = next, errorMessage = null)
        if (next.length >= MIN_PIN_LENGTH) {
            attemptVerify(next)
        }
    }

    fun onBackspace() {
        val current = _state.value
        if (current.verifying) return
        _state.value = current.copy(
            enteredDigits = current.enteredDigits.dropLast(1),
            errorMessage = null,
        )
    }

    fun onClear() {
        if (_state.value.verifying) return
        _state.value = _state.value.copy(enteredDigits = "", errorMessage = null)
    }

    fun consumeVerified() {
        _state.value = _state.value.copy(verified = null, enteredDigits = "")
    }

    private fun attemptVerify(plain: String) {
        _state.value = _state.value.copy(verifying = true)
        viewModelScope.launch {
            val match = runCatching { staffPinRepository.verifyPin(plain) }
                .getOrNull()
            _state.value = if (match != null) {
                _state.value.copy(
                    verifying = false,
                    verified = VerifiedPin(id = match.id, name = match.name),
                )
            } else {
                _state.value.copy(
                    verifying = false,
                    enteredDigits = "",
                    errorMessage = ErrorReason.WrongPin,
                )
            }
        }
    }

    companion object {
        private const val MIN_PIN_LENGTH = 4
        private const val MAX_PIN_LENGTH = 6
    }
}

data class PinUiState(
    val loading: Boolean = true,
    val pins: List<PinForDeviceItem> = emptyList(),
    val enteredDigits: String = "",
    val errorMessage: ErrorReason? = null,
    val verifying: Boolean = false,
    val verified: VerifiedPin? = null,
    val pendingSyncCount: Int = 0,
    val isOnline: Boolean = true,
)

enum class ErrorReason {
    WrongPin,
    Locked,
    Unknown,
}

data class VerifiedPin(
    val id: Int,
    val name: String,
)
