package com.isidroid.c23.ui.screen.map

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.isidroid.c23.constant.Argument
import com.isidroid.c23.data.mapper.transformToMapMarker
import com.isidroid.c23.domain.use_case.MapUseCase
import com.isidroid.c23.ext.isDebug
import com.isidroid.core.FlowResult
import com.isidroid.core.vm.BaseViewModel
import com.isidroid.location.model.LatLng2
import com.isidroid.spot.model.RichSpot
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.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject

@HiltViewModel
class MapViewModel @Inject constructor(
    private val useCase: MapUseCase,
    savedStateHandle: SavedStateHandle
) : BaseViewModel<MapContract.Event, MapContract.State, MapContract.Effect>() {
    private var _findSpotsJob: Job? = null
    private val _data = hashMapOf<String, RichSpot>()
    private var _markers = hashMapOf<String, List<RichSpot>>()
    private var _uris: String? = null

    private val _spotResultStateFlow = MutableStateFlow<List<MapMarker>>(emptyList())
    val spotResultStateFlow = _spotResultStateFlow.asStateFlow()

    init {
        viewModelScope.launch { listenArguments(savedStateHandle) }
    }

    override val isDebug: Boolean = isDebug()
    override fun setInitialState() = MapContract.State()
    override suspend fun handleEvents(event: MapContract.Event) {
        when (event) {
            MapContract.Event.PermissionGranted -> prepareData(viewState.value.lat, viewState.value.lng, viewState.value.spotCode)
            is MapContract.Event.CameraMove -> onCameraMoved(event.lat, event.lng, event.radius)
            is MapContract.Event.ClickOnMarker -> clickOnMarker(event.markerId)
            is MapContract.Event.SelectSpot -> selectSpot(event.id)
            MapContract.Event.ToBack -> setEffect { MapContract.Effect.Navigation.ToBack }
            MapContract.Event.MoveCameraToMyLocation -> moveToMyLocation()
        }
    }

    // handle events
    private suspend fun listenArguments(savedStateHandle: SavedStateHandle) {
        val flowLat = savedStateHandle.getStateFlow<String?>(Argument.LATITUDE, null)
        val flowLng = savedStateHandle.getStateFlow<String?>(Argument.LONGITUDE, null)
        val flowSpotCode = savedStateHandle.getStateFlow<String?>(Argument.SPOT_CODE, null)
        val flowUris = savedStateHandle.getStateFlow<String?>(Argument.URI, null)

        combine(flowLat, flowLng, flowSpotCode, flowUris) { lat, lng, spotCode, uris ->
            _uris = uris
            prepareData(
                lat = lat?.toDoubleOrNull(),
                lng = lng?.toDoubleOrNull(),
                spotCode = spotCode
            )
        }.collect()
    }

    private suspend fun prepareData(lat: Double?, lng: Double?, spotCode: String?) {
        useCase.prepareData(lat, lng, spotCode)
            .flowOn(Dispatchers.IO)
            .collect { state ->
                when (state) {
                    MapUseCase.MapPrepareDto.NoPermissions -> setState { copy(requestLocationPermission = true, lat = lat, lng = lng, spotCode = spotCode) }
                    is MapUseCase.MapPrepareDto.Location -> setState { copy(requestLocationPermission = false, lat = state.lat, lng = state.lng) }
                }
            }
    }

    private fun onCameraMoved(lat: Double, lng: Double, radius: Int) {
        _findSpotsJob?.cancel()
        _findSpotsJob = viewModelScope.launch {
            useCase.findSpots(lat, lng, radius)
                .flowOn(Dispatchers.IO)
                .catchTimber { }
                .collect { res ->
                    when (res) {
                        FlowResult.Loading -> {}
                        is FlowResult.Success -> locationsLoaded(res.result)
                    }
                }
        }
    }

    private fun clickOnMarker(markerId: String?) {
        val spots = _markers[markerId]
        setState { copy(spotsInfoList = spots) }
    }

    private suspend fun selectSpot(id: String) {
        useCase.selectSpot(_data[id])
            .flowOn(Dispatchers.IO)
            .collect { onSpotSelected() }
    }

    private suspend fun moveToMyLocation() {
        useCase.myLocation().flowOn(Dispatchers.IO).collect {
            setState { copy(lat = it.first, lng = it.second) }
        }
    }

    // callbacks
    private suspend fun locationsLoaded(result: Map<LatLng2, List<RichSpot>>) {
        result.forEach { entry ->
            for (item in entry.value)
                _data[item.spot.id] = item
        }

        _markers.clear()
        val markers = result.map {
            val id = UUID.randomUUID().toString()
            _markers[id] = it.value
            it.key.transformToMapMarker(id)
        }

        _spotResultStateFlow.emit(markers)
    }

    private fun onSpotSelected() {
        val effect = _uris?.let { MapContract.Effect.Navigation.ToPreview(uris = it) } ?: MapContract.Effect.Navigation.ToSelectContent

        _uris = null
        setEffect { effect }
    }
}