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.search.GetFlightsSearchContentUseCase
import dev.adriankuta.flights.domain.search.ObserveAirportsUseCase
import dev.adriankuta.flights.domain.search.entities.SearchOptions
import dev.adriankuta.flights.domain.types.Airport
import dev.adriankuta.flights.domain.types.AirportInfo
import dev.adriankuta.flights.domain.types.Flight
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.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.todayIn
import javax.inject.Inject

@HiltViewModel
class HomeScreenViewModel @Inject constructor(
    observeAirportsUseCase: ObserveAirportsUseCase,
    private val getFlightsSearchContentUseCase: GetFlightsSearchContentUseCase,
) : ViewModel() {

    private val selectedOriginAirport = MutableStateFlow<AirportInfo?>(null)
    private val selectedDestinationAirport = MutableStateFlow<AirportInfo?>(null)
    private val selectedDate =
        MutableStateFlow(Clock.System.todayIn(TimeZone.currentSystemDefault()))
    private val adultCount = MutableStateFlow(1)
    private val teenCount = MutableStateFlow(0)
    private val childCount = MutableStateFlow(0)
    private val searchResults = MutableStateFlow<SearchResultUiState>(SearchResultUiState.Idle)

    internal val uiState = homeUiState(
        useCase = observeAirportsUseCase,
        selectedOriginAirport = selectedOriginAirport,
        selectedDestinationAirport = selectedDestinationAirport,
        selectedDate = selectedDate,
        passengersState = passengersState(
            adultCount = adultCount,
            teenCount = teenCount,
            childCount = childCount,
        ),
    )
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = HomeUiState.Loading,
        )

    internal val searchResultUiState = searchResults
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = SearchResultUiState.Idle,
        )

    fun selectOriginAirport(airport: AirportInfo) {
        selectedOriginAirport.value = airport
        // If the selected destination is the same as the new origin, clear the destination
        if (selectedDestinationAirport.value?.code == airport.code) {
            selectedDestinationAirport.value = null
        }
    }

    fun selectDestinationAirport(airport: AirportInfo) {
        selectedDestinationAirport.value = airport
    }

    fun clearOriginAirport() {
        selectedOriginAirport.value = null
        // Clear destination as well since it depends on origin
        selectedDestinationAirport.value = null
    }

    fun clearDestinationAirport() {
        selectedDestinationAirport.value = null
    }

    fun selectDate(date: LocalDate) {
        selectedDate.value = date
    }

    fun updateAdultCount(count: Int) {
        adultCount.value = count
        childCount.value = childCount.value.coerceAtMost(count)
    }

    fun updateTeenCount(count: Int) {
        teenCount.value = count
    }

    fun updateChildCount(count: Int) {
        childCount.value = count
    }

    fun search() {
        searchResults.value = SearchResultUiState.Loading
        viewModelScope.launch {
            try {
                val results = getFlightsSearchContentUseCase(
                    searchOptions = prepareSearchOptions(),
                )
                searchResults.value = SearchResultUiState.Success(results)
            } catch (e: IllegalArgumentException) {
                searchResults.value = SearchResultUiState.Error(e)
            }
        }
    }

    private fun prepareSearchOptions(): SearchOptions {
        val selectedOrigin = requireNotNull(selectedOriginAirport.value)
        val selectedDestination = requireNotNull(selectedDestinationAirport.value)

        return SearchOptions(
            origin = Airport.Departure(
                code = selectedOrigin.code,
                name = selectedOrigin.name,
                macCity = selectedOrigin.macCity,
            ),
            destination = Airport.Arrival(
                code = selectedDestination.code,
                name = selectedDestination.name,
                macCity = selectedDestination.macCity,
            ),
            date = selectedDate.value,
            adults = adultCount.value,
            teens = teenCount.value,
            children = childCount.value,
        )
    }
}

private fun homeUiState(
    useCase: ObserveAirportsUseCase,
    selectedOriginAirport: StateFlow<AirportInfo?>,
    selectedDestinationAirport: StateFlow<AirportInfo?>,
    selectedDate: StateFlow<LocalDate>,
    passengersState: Flow<PassengersState>,
): Flow<HomeUiState> {
    return combine(
        useCase().asResult(),
        selectedOriginAirport,
        selectedDestinationAirport,
        selectedDate,
        passengersState,
    ) { result, origin, destination, date, passengers ->
        when (result) {
            is Result.Error -> HomeUiState.Error(result.exception)
            is Result.Loading -> HomeUiState.Loading
            is Result.Success -> {
                val airports = result.data.orEmpty()
                HomeUiState.Success(
                    originAirports = airports,
                    destinationAirports = airports.filter { it.code != origin?.code },
                    selectedOriginAirport = origin,
                    selectedDestinationAirport = destination,
                    selectedDate = date,
                    passengers = passengers,
                )
            }
        }
    }
}

private fun passengersState(
    adultCount: StateFlow<Int>,
    teenCount: StateFlow<Int>,
    childCount: StateFlow<Int>,
): Flow<PassengersState> {
    return combine(
        adultCount,
        teenCount,
        childCount,
    ) { adults, teens, children ->
        PassengersState(
            adultCount = adults,
            teenCount = teens,
            childCount = children,
        )
    }
}

internal sealed interface HomeUiState {
    data object Loading : HomeUiState
    data class Success(
        val originAirports: List<AirportInfo>,
        val destinationAirports: List<AirportInfo>,
        val selectedOriginAirport: AirportInfo? = null,
        val selectedDestinationAirport: AirportInfo? = null,
        val selectedDate: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()),
        val passengers: PassengersState,
    ) : HomeUiState

    data class Error(val exception: Throwable) : HomeUiState
}

internal sealed interface SearchResultUiState {
    data object Loading : SearchResultUiState
    data class Success(val flight: Flight) : SearchResultUiState
    data class Error(val exception: Throwable) : SearchResultUiState
    data object Idle : SearchResultUiState
}

internal data class PassengersState(
    val adultCount: Int,
    val teenCount: Int,
    val childCount: Int,
)
