package ru.fincode.tsudesk.feature.schedule.presentation.screen

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import ru.fincode.tsudesk.core.common.log.logD
import ru.fincode.tsudesk.core.common.log.logE
import ru.fincode.tsudesk.core.common.model.DataResult
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleType
import ru.fincode.tsudesk.feature.schedule.domain.usecase.ObserveScheduleUseCase
import ru.fincode.tsudesk.feature.schedule.presentation.util.buildDayStrip
import ru.fincode.tsudesk.feature.schedule.presentation.util.toUiMessage
import ru.fincode.tsudesk.feature.schedule.presentation.util.todayString
import javax.inject.Inject

@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class ScheduleViewModel @Inject constructor(
    private val observeScheduleUseCase: ObserveScheduleUseCase
) : ViewModel() {

    private val _state = MutableStateFlow(
        ScheduleUiState(
            selectedDate = todayString(),
            dayStrip = buildDayStrip(todayString())
        )
    )
    val state: StateFlow<ScheduleUiState> = _state.asStateFlow()

    init {
        logD("ScheduleViewModel init. selectedDate=${_state.value.selectedDate}")
        _state
            .map { it.selectedType }
            .distinctUntilChanged()
            .onEach { type -> logD("Observe schedule: type=$type key=${type.toCacheKey()}") }
            .flatMapLatest { type -> observeScheduleUseCase(type) }
            .onEach { reduce(it) }
            .onCompletion { cause ->
                // страховка: если поток завершился и loading остался висеть
                if (_state.value.isLoading) {
                    logD("Observe completed. cause=${cause?.message}. Stop loading.")
                    _state.update { it.copy(isLoading = false) }
                }
            }
            .catch { e ->
                logE("UseCase flow error", e)
                _state.update {
                    it.copy(
                        isLoading = false,
                        error = e.message ?: "Ошибка"
                    )
                }
            }
            .launchIn(viewModelScope)
    }

    fun onIntent(intent: ScheduleIntent) {
        when (intent) {
            is ScheduleIntent.ChangeGroupInput ->
                _state.update { it.copy(groupInput = intent.value) }

            is ScheduleIntent.SetGroupMenuExpanded ->
                _state.update { it.copy(isGroupMenuExpanded = intent.expanded) }

            is ScheduleIntent.SelectRecentGroup -> {
                logD("SelectRecentGroup: ${intent.number}")
                _state.update {
                    it.copy(
                        groupInput = intent.number,
                        isGroupMenuExpanded = false
                    )
                }
            }

            is ScheduleIntent.RemoveRecentGroup -> {
                logD("RemoveRecentGroup: ${intent.number}")
                _state.update {
                    it.copy(recentGroups = it.recentGroups.filterNot { g -> g == intent.number })
                }
            }

            ScheduleIntent.ApplyGroup -> {
                logD("ApplyGroup clicked")
                applyGroupAndReload()
            }

            is ScheduleIntent.SelectDate -> {
                _state.update {
                    it.copy(
                        selectedDate = intent.date,
                        dayStrip = buildDayStrip(intent.date)
                    )
                }
                remapIfPossible()
            }

            is ScheduleIntent.SetViewMode -> {
                _state.update { it.copy(viewMode = intent.mode) }
                remapIfPossible()
            }

            ScheduleIntent.Retry -> {
                // Ретрай можно сделать через "переприсвоение" selectedType (триггер distinctUntilChanged не пропустит)
                // поэтому делаем принудительный рефреш: сбрасываем raw/error + ставим loading,
                // а реальную перезагрузку должен инициировать usecase репозитория (если он умеет).
                // Если в usecase нет принудительного refresh — оставляем как UI-retry (сброс ошибки).
                logD("Retry requested")
                _state.update { it.copy(error = null) }
                // Если нужен именно network refresh — лучше добавить отдельный intent/usecase:
                // observeScheduleUseCase.refresh(selectedType)
            }
        }
    }

    private fun applyGroupAndReload() {
        val current = _state.value
        if (current.isLoading) {
            logD("ApplyGroup ignored: already loading")
            return
        }

        val number = current.groupInput.trim()
        if (number.isEmpty()) {
            _state.update { it.copy(error = "Введите номер группы") }
            return
        }

        val type = ScheduleType.Group(number)
        logD("Start load schedule for group=$number key=${type.toCacheKey()}")

        // При смене группы очищаем данные + обновляем selectedType.
        // Наблюдение перезапустится автоматически (flatMapLatest по selectedType).
        _state.update {
            it.copy(
                selectedType = type,
                recentGroups = (listOf(number) + it.recentGroups).distinct().take(8),
                isGroupMenuExpanded = false,
                error = null,
                raw = null,
                isLoading = true, // визуально сразу показываем загрузку
                dayLessons = emptyList(),
                weekGrouped = emptyList()
            )
        }
    }

    private fun reduce(result: DataResult<ScheduleEntity>) {
        when (result) {
            DataResult.Loading -> {
                logD("DataResult.Loading")
                _state.update { it.copy(isLoading = true, error = null) }
            }

            is DataResult.Data -> {
                val source = if (result.refreshedFromNetwork) "NETWORK" else "CACHE"
                logD("DataResult.Data received. Source=$source lessons=${result.data.lessons.size}")

                _state.update { it.copy(isLoading = false, error = null, raw = result) }
                remapWithEntity(result.data)
            }

            is DataResult.Error -> {
                logE("DataResult.Error received. error=${result.error}")

                val cached = result.data
                _state.update {
                    it.copy(
                        isLoading = false,
                        error = result.error.toUiMessage(),
                        raw = result
                    )
                }

                if (cached != null) {
                    logD("Using cached data. lessons=${cached.lessons.size}")
                    remapWithEntity(cached)
                }
            }
        }
    }

    private fun remapIfPossible() {
        val raw = _state.value.raw
        val entity = when (raw) {
            is DataResult.Data -> raw.data
            is DataResult.Error -> raw.data
            else -> null
        } ?: return

        logD("RemapIfPossible triggered")
        remapWithEntity(entity)
    }

    private fun remapWithEntity(entity: ScheduleEntity) {
        val selectedDate = _state.value.selectedDate
        val strip = _state.value.dayStrip

        val dayLessons = ScheduleUiMapper.mapDayLessons(entity, selectedDate)
        val weekGrouped = ScheduleUiMapper.mapWeekGrouped(entity, strip)

        logD("Remap complete: dayLessons=${dayLessons.size}, weekDays=${weekGrouped.size}")

        _state.update {
            it.copy(
                dayLessons = dayLessons,
                weekGrouped = weekGrouped
            )
        }
    }
}