package com.isidroid.c23.ui.screen.details

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccountCircle
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.isidroid.c23.NoLocationPermissionException
import com.isidroid.c23.constant.Argument
import com.isidroid.c23.domain.dto.PrintJobListItem
import com.isidroid.c23.domain.use_case.DetailsUseCase
import com.isidroid.c23.ext.isDebug
import com.isidroid.core.FlowResult
import com.isidroid.core.vm.BaseViewModel
import com.isidroid.job_sender.SendJobEventCollectorFlow
import com.isidroid.job_sender.domain.dto.JobSenderResult
import com.isidroid.ui.maps.model.MapMarker
import com.isidroid.utils.catchTimber
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.singleOrNull
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class JobDetailsViewModel @Inject constructor(
    private val useCase: DetailsUseCase,
    private val sendJobEventCollectorFlow: SendJobEventCollectorFlow,
    savedStateHandle: SavedStateHandle
) : BaseViewModel<JobDetailsContract.Event, JobDetailsContract.State, JobDetailsContract.Effect>() {
    private val _spotResultStateFlow = MutableStateFlow<List<MapMarker>>(emptyList())
    val spotResultStateFlow = _spotResultStateFlow.asStateFlow()

    private lateinit var _jobId: String

    init {
        viewModelScope.launch { create(savedStateHandle) }
        viewModelScope.launch { listenSendJobEvents() }
    }


    override val isDebug: Boolean = isDebug()
    override fun setInitialState(): JobDetailsContract.State = JobDetailsContract.State()
    override suspend fun handleEvents(event: JobDetailsContract.Event) {
        when (event) {
            JobDetailsContract.Event.ToBack -> setEffect { JobDetailsContract.Effect.Navigation.ToBack }
            JobDetailsContract.Event.OpenConfirmationMapRoute -> setState { copy(routeConfirmationVisible = true) }
            JobDetailsContract.Event.DismissBuildRouteConfirmation -> setState { copy(routeConfirmationVisible = false) }
            JobDetailsContract.Event.OpenNavigationApp -> openNavigationApp()
            JobDetailsContract.Event.LocationPermissionGranted -> locationPermissionGranted()
            JobDetailsContract.Event.CheckNotificationPermission -> setState { copy(hasNotificationPermission = null) }
        }
    }

    // handle events
    private suspend fun create(savedStateHandle: SavedStateHandle) {
        savedStateHandle.getStateFlow<String?>(Argument.ID, null)
            .filterNotNull()
            .onEach {
                _jobId = it
                loadDetails()
            }
            .singleOrNull()
    }

    private suspend fun listenSendJobEvents() {
        sendJobEventCollectorFlow.eventsFlow
            .collect { st ->
                when (st) {
                    is JobSenderResult.Error -> {}
                    is JobSenderResult.RenderProgress -> onRenderProgress(st.jobId, st.position, st.total)
                    is JobSenderResult.Statuses -> updateStatuses(st)
                    is JobSenderResult.UploadProgress -> onUploadProgress(st.jobId, st.position, st.total)
                }
            }
    }

    private suspend fun loadDetails() {
        useCase.loadDetails(_jobId)
            .flowOn(Dispatchers.IO)
            .catchTimber {
                when (it) {
                    is NoLocationPermissionException -> setState { copy(requestLocationPermission = true) }
                }
            }
            .collect { res ->
                when (res) {
                    FlowResult.Loading -> setState { copy(isLoading = true) }
                    is FlowResult.Success -> onDetails(
                        item = res.result[Argument.INFO] as? PrintJobListItem,
                        lat = res.result[Argument.LATITUDE] as? Double,
                        lng = res.result[Argument.LONGITUDE] as? Double,
                        mapMarker = res.result[Argument.MARKER] as? MapMarker,
                        distance = res.result[Argument.DISTANCE] as? String,
                        hasNotificationPermission = res.result[Argument.HAS_NOTIFICATION_PERMISSION] as? Boolean
                    )
                }
            }
    }

    private suspend fun onDetails(item: PrintJobListItem?,
                                  lat: Double?,
                                  lng: Double?,
                                  mapMarker: MapMarker?,
                                  distance: String?,
                                  hasNotificationPermission: Boolean?) {
        val myLocation = MapMarker(id = "my_location", lat = lat ?: 0.0, lng = lng ?: 0.0, name = "My location", imageVector = Icons.Outlined.AccountCircle)
        _spotResultStateFlow.emit(listOfNotNull(mapMarker, myLocation))

        setState { copy(isLoading = false, printJob = item, lat = lat, lng = lng, distance = distance, hasNotificationPermission = hasNotificationPermission) }
    }

    private fun openNavigationApp() {
        setState { copy(routeConfirmationVisible = false) }
        setEffect { JobDetailsContract.Effect.Navigation.ToNavigationApp(viewState.value.lat, viewState.value.lng) }
    }

    private suspend fun locationPermissionGranted() {
        setState { copy(requestLocationPermission = false) }
        loadDetails()
    }

    private fun updateStatuses(statuses: JobSenderResult.Statuses) {
        val updated = useCase.updateStatuses(viewState.value.printJob, info = statuses) ?: return
        setState { copy(printJob = updated) }
    }

    private fun onRenderProgress(jobId: String, position: Int, total: Int) {
        if (jobId != _jobId) return
        setState { copy(renderProgress = Pair(position, total), uploadProgress = null) }
    }

    private fun onUploadProgress(jobId: String?, position: Int, total: Int) {
        if (jobId != _jobId) return
        setState { copy(uploadProgress = Pair(position, total), renderProgress = null) }
    }
}