package dev.adriankuta.flights.ui.home

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.adriankuta.flights.core.util.Result
import dev.adriankuta.flights.core.util.asResult
import dev.adriankuta.flights.domain.stations.GetConnectionsForAirportUseCase
import dev.adriankuta.flights.domain.stations.ObserveAirportsGroupedByCountry
import dev.adriankuta.flights.domain.types.Airport
import dev.adriankuta.flights.domain.types.AirportInfo
import dev.adriankuta.flights.domain.types.Country
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import javax.inject.Inject

@HiltViewModel
internal class StationsScreenViewModel @Inject constructor(
    observeAirportsGroupedByCountry: ObserveAirportsGroupedByCountry,
    getConnectionsForAirportUseCase: GetConnectionsForAirportUseCase,
) : ViewModel() {

    private val _searchQuery = MutableStateFlow("")
    val searchQuery: StateFlow<String> = _searchQuery

    private val _selectedOriginAirport = MutableStateFlow<AirportInfo?>(null)
    val selectedOriginAirport: StateFlow<AirportInfo?> = _selectedOriginAirport
    private val _availableDestinationsCodes = selectedOriginAirport.transformLatest { airportInfo ->
        val result = airportInfo?.let { nonNullAirportInfo ->
            getConnectionsForAirportUseCase(nonNullAirportInfo.toDepartureAirPort())
                .filter { it is Airport.Arrival }
                .map { it.code }
        }.orEmpty()
        emit(result)
    }

    fun onSearchQueryChanged(query: String) {
        _searchQuery.value = query
    }

    fun onAirportSelected(airport: AirportInfo) {
        _selectedOriginAirport.value = airport
    }

    fun clearSelectedAirport() {
        _selectedOriginAirport.value = null
    }

    val originAirports = airportsUiState(
        useCase = observeAirportsGroupedByCountry,
        searchQuery = searchQuery,
        filterDestinations = _availableDestinationsCodes,
    ).stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = AirportsUiState.Loading,
    )
}

private fun airportsUiState(
    useCase: ObserveAirportsGroupedByCountry,
    searchQuery: StateFlow<String>,
    filterDestinations: Flow<List<String>>,
): Flow<AirportsUiState> {
    return combine(
        useCase().asResult(),
        searchQuery,
        filterDestinations,
    ) { result, query, destinationCodes ->
        when (result) {
            is Result.Error -> AirportsUiState.Error(result.exception)
            is Result.Loading -> AirportsUiState.Loading
            is Result.Success -> {
                val airports = result.data.orEmpty()
                if (query.isBlank() && destinationCodes.isEmpty()) {
                    AirportsUiState.Success(airports)
                } else {
                    val filteredAirports = airports.mapValues { (_, airportList) ->
                        airportList.asSequence()
                            .filter { destinationCodes.isEmpty() || destinationCodes.contains(it.code) }
                            .filter { airport ->
                                airport.name.contains(query, ignoreCase = true) ||
                                    airport.code.contains(query, ignoreCase = true) ||
                                    airport.city.name.contains(query, ignoreCase = true) ||
                                    airport.country.name.contains(query, ignoreCase = true)
                            }
                            .toList()
                    }.filterValues { it.isNotEmpty() }
                    AirportsUiState.Success(filteredAirports)
                }
            }
        }
    }
}

private fun AirportInfo.toDepartureAirPort(): Airport.Departure =
    Airport.Departure(
        code = code,
        name = name,
        macCity = macCity,
    )

internal sealed interface AirportsUiState {
    data object Loading : AirportsUiState
    data class Success(val groupedAirports: Map<Country, List<AirportInfo>>) : AirportsUiState
    data class Error(val exception: Throwable) : AirportsUiState
}
