package lu.rewardflow.terminal.ui.setup

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import lu.rewardflow.terminal.data.api.LoyaltyApi
import lu.rewardflow.terminal.data.model.ProgramResponse
import lu.rewardflow.terminal.data.model.SetupPayload
import lu.rewardflow.terminal.data.repository.CategoryRepository
import lu.rewardflow.terminal.data.repository.DeviceConfigRepository
import lu.rewardflow.terminal.data.repository.StaffPinRepository
import javax.inject.Inject

@HiltViewModel
class SetupViewModel @Inject constructor(
    private val configRepository: DeviceConfigRepository,
    private val api: LoyaltyApi,
    private val staffPinRepository: StaffPinRepository,
    private val categoryRepository: CategoryRepository,
    moshi: Moshi,
) : ViewModel() {

    /** Observed by [RewardFlowNavHost] to pick the start destination. */
    val isDeviceSetUp: Flow<Boolean> = configRepository.isDeviceSetUp

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

    private val payloadAdapter: JsonAdapter<SetupPayload> =
        moshi.adapter(SetupPayload::class.java)

    private val programAdapter: JsonAdapter<ProgramResponse> =
        moshi.adapter(ProgramResponse::class.java)

    /** Decode the JSON encoded in the pairing QR and run the pair flow. */
    fun pairFromQr(qrRawValue: String) {
        val payload = try {
            payloadAdapter.fromJson(qrRawValue)
        } catch (_: JsonDataException) {
            null
        } catch (_: java.io.IOException) {
            null
        }
        if (payload == null) {
            _state.value = SetupState.Error(SetupError.InvalidQr)
            return
        }
        pairWith(payload)
    }

    /** Manual entry path — same flow as [pairFromQr] but with a hand-typed payload.
     *
     * Used during dev/testing when there is no QR generator handy. Production
     * users always pair via the QR. ``storeId`` is required because the device
     * needs the numeric id alongside the code (some endpoints use one, some
     * use the other).
     */
    fun pairManually(apiUrl: String, storeId: Int, storeCode: String, authToken: String) {
        if (apiUrl.isBlank() || storeCode.isBlank() || authToken.isBlank() || storeId <= 0) {
            _state.value = SetupState.Error(SetupError.InvalidQr)
            return
        }
        pairWith(
            SetupPayload(
                api_url = apiUrl,
                store_id = storeId,
                store_code = storeCode,
                store_name = null,
                auth_token = authToken,
            )
        )
    }

    fun resetError() {
        if (_state.value is SetupState.Error) _state.value = SetupState.Idle
    }

    private fun pairWith(payload: SetupPayload) {
        _state.value = SetupState.Connecting
        viewModelScope.launch {
            // 1. Persist the pairing FIRST so the AuthInterceptor will use the new
            //    api_url and bearer token on the verification call below.
            configRepository.savePairing(
                apiUrl = payload.api_url,
                authToken = payload.auth_token,
                storeId = payload.store_id,
                storeCode = payload.store_code,
                storeName = payload.store_name,
            )

            // 2. Verify by hitting /program. Failure → roll back the pairing
            //    so the user is back at a clean Setup screen with an error.
            val program: ProgramResponse = try {
                api.getProgram()
            } catch (e: Exception) {
                configRepository.resetDevice()
                _state.value = SetupState.Error(SetupError.ConnectionFailed(e.message))
                return@launch
            }

            configRepository.saveProgram(programAdapter.toJson(program))

            // 4. Warm caches. Failures here are non-fatal — the screens will
            //    refresh on their own when needed.
            runCatching { staffPinRepository.refresh() }
            runCatching { categoryRepository.refresh() }

            _state.value = SetupState.Success
        }
    }
}

sealed interface SetupState {
    data object Idle : SetupState
    data object Connecting : SetupState
    data object Success : SetupState
    data class Error(val reason: SetupError) : SetupState
}

sealed interface SetupError {
    data object InvalidQr : SetupError
    data class ConnectionFailed(val detail: String?) : SetupError
}
