package pl.firmatpp.kierowca.ui

import android.app.Application
import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.google.firebase.messaging.FirebaseMessaging
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.time.LocalDate
import java.time.Instant
import pl.firmatpp.kierowca.data.AppPreferencesStore
import pl.firmatpp.kierowca.data.ApiErrorMapper
import pl.firmatpp.kierowca.data.ApiErrorKind
import pl.firmatpp.kierowca.data.DriverRepository
import pl.firmatpp.kierowca.data.PhotoUploadMetadata
import pl.firmatpp.kierowca.data.sync.DriverSyncRepository
import pl.firmatpp.kierowca.data.sync.NetworkMonitor
import pl.firmatpp.kierowca.data.model.DispatchSheetReminderDto
import pl.firmatpp.kierowca.data.model.DriverDto
import pl.firmatpp.kierowca.data.model.DriverLeaveCalendarEntryDto
import pl.firmatpp.kierowca.data.model.DriverLeaveRequestDto
import pl.firmatpp.kierowca.data.model.DriverRouteDto
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
import pl.firmatpp.kierowca.data.upload.DispatchSheetUploadEntity
import pl.firmatpp.kierowca.data.upload.DispatchSheetUploadOutbox
import pl.firmatpp.kierowca.data.upload.PhotoUploadEntity
import pl.firmatpp.kierowca.data.upload.PhotoUploadOutbox
import pl.firmatpp.kierowca.data.upload.RouteActionEntity
import pl.firmatpp.kierowca.data.upload.RouteActionOutbox
import pl.firmatpp.kierowca.diagnostics.AppDiagnostics
import pl.firmatpp.kierowca.sync.DriverLiveSyncClient
import pl.firmatpp.kierowca.sync.DriverSyncHint
import pl.firmatpp.kierowca.sync.DriverSyncWorker
import pl.firmatpp.kierowca.tracking.ActiveRouteTrackingService
import pl.firmatpp.kierowca.ui.theme.AppThemeMode

enum class DriverScreen { Initializing, Phone, Otp, Routes, Profile, Detail, StartRoute, LoadingWeight, FinishRoute, Photo, PhotoQueue, LeaveRequests, LeaveRequestDetail, LeaveCalendar, AddLeaveRequest }

data class DriverUiState(
    val screen: DriverScreen = DriverScreen.Initializing,
    val loading: Boolean = true,
    val refreshing: Boolean = false,
    val phone: String = "",
    val otpCode: String = "",
    val maskedPhone: String = "",
    val driver: DriverDto? = null,
    val routes: List<DriverRouteDto> = emptyList(),
    val selectedDate: String = LocalDate.now().toString(),
    val minRouteDate: String = LocalDate.now().minusDays(7).toString(),
    val maxRouteDate: String = LocalDate.now().plusDays(1).toString(),
    val autoRefreshSeconds: Int = 60,
    val allowGalleryUploads: Boolean = true,
    val allowRouteCompletion: Boolean = false,
    val requirePreciseLocationForPhotos: Boolean = false,
    val notifyNewRoutes: Boolean = false,
    val routeProgressNotificationEnabled: Boolean = true,
    val notificationPermissionDenied: Boolean = false,
    val selectedRoute: DriverRouteDto? = null,
    val selectedPhoto: RoutePhotoDto? = null,
    val routeStageWeightText: String = "",
    val routeActions: List<RouteActionEntity> = emptyList(),
    val visibleRouteActions: List<RouteActionEntity> = emptyList(),
    val photoUploads: List<PhotoUploadEntity> = emptyList(),
    val queuedPhotoUploads: List<PhotoUploadEntity> = emptyList(),
    val dispatchSheetReminder: DispatchSheetReminderDto? = null,
    val dispatchSheetUploads: List<DispatchSheetUploadEntity> = emptyList(),
    val leaveRequestsConfig: LeaveRequestsConfig? = null,
    val leaveRequests: List<DriverLeaveRequestDto> = emptyList(),
    val selectedLeaveRequest: DriverLeaveRequestDto? = null,
    val leaveRequestDateFrom: String = LocalDate.now().toString(),
    val leaveRequestDateTo: String = LocalDate.now().toString(),
    val leaveRequestType: String = "URLOP",
    val leaveRequestNote: String = "",
    val leaveCalendarEntries: List<DriverLeaveCalendarEntryDto> = emptyList(),
    val leaveCalendarLoading: Boolean = false,
    val leaveCalendarLoadedUntil: String? = null,
    val leaveCalendarHasSelection: Boolean = false,
    val leaveCalendarSelectionAnchor: String? = null,
    val deletingPhotoIds: Set<String> = emptySet(),
    val completingRoute: Boolean = false,
    val imageAuthHeader: String? = null,
    val isOnline: Boolean = true,
    val isStale: Boolean = false,
    val lastSuccessfulSyncAtEpochMillis: Long? = null,
    val routeDayLiveUpdateMessage: String? = null,
    val themeMode: AppThemeMode = AppThemeMode.Default,
    val feedback: String? = null,
    val error: String? = null,
) {
    private val projectionActions: List<RouteActionEntity>
        get() = (visibleRouteActions + routeActions).distinctBy { it.clientActionId }

    val displayRoutes: List<DriverRouteDto>
        get() = projectDriverRoutes(routes, projectionActions)

    val selectedRouteProjection: ProjectedDriverRoute?
        get() = selectedRoute?.let { projectDriverRoute(it, projectionActions) }

    val displaySelectedRoute: DriverRouteDto?
        get() = selectedRouteProjection?.route
}

private fun DriverUiState.withApiError(throwable: Throwable): DriverUiState {
    val apiError = ApiErrorMapper.map(throwable)
    return copy(
        isOnline = if (apiError.kind == ApiErrorKind.Network) false else isOnline,
        isStale = if (apiError.kind == ApiErrorKind.Network && lastSuccessfulSyncAtEpochMillis != null) true else isStale,
        error = apiError.message,
    )
}

internal fun DriverUiState.withLoadedLeaveRequests(
    requests: List<DriverLeaveRequestDto>,
    navigateToList: Boolean,
): DriverUiState {
    val refreshedSelectedRequest = selectedLeaveRequest?.let { selected ->
        requests.firstOrNull { request -> request.id == selected.id } ?: selected
    }
    return copy(
        screen = if (navigateToList) DriverScreen.LeaveRequests else screen,
        leaveRequests = requests,
        selectedLeaveRequest = refreshedSelectedRequest,
        error = null,
    )
}

class DriverViewModel(application: Application) : AndroidViewModel(application) {
    private val appPreferencesStore = AppPreferencesStore(application)
    private val repository = DriverRepository(application)
    private val syncRepository = DriverSyncRepository(application, repository)
    private val networkMonitor = NetworkMonitor(application)
    private val photoUploadOutbox = PhotoUploadOutbox(application)
    private val routeActionOutbox = RouteActionOutbox(application)
    private val dispatchSheetUploadOutbox = DispatchSheetUploadOutbox(application)
    private val _state = MutableStateFlow(DriverUiState(loading = true))
    private val otpAutoSubmitPolicy = OtpAutoSubmitPolicy()
    private val liveSyncClient = DriverLiveSyncClient(
        repository = repository,
        onConnected = { viewModelScope.launch { checkRemoteSyncState() } },
        onHint = { hint -> viewModelScope.launch { handleSyncHint(hint) } },
    )
    private var photoUploadsJob: Job? = null
    private var routeActionsJob: Job? = null
    private var dispatchSheetUploadsJob: Job? = null
    private var pushTokenRegisteredForDriverId: String? = null
    val state: StateFlow<DriverUiState> = _state

    init {
        viewModelScope.launch {
            appPreferencesStore.themeMode.collect { themeMode ->
                _state.update { it.copy(themeMode = themeMode) }
            }
        }
        viewModelScope.launch {
            appPreferencesStore.routeProgressNotificationEnabled.collect { enabled ->
                _state.update { it.copy(routeProgressNotificationEnabled = enabled) }
            }
        }
        viewModelScope.launch {
            photoUploadOutbox.observeQueuedUploads().collect { uploads ->
                _state.update { it.copy(queuedPhotoUploads = uploads) }
            }
        }
        viewModelScope.launch {
            routeActionOutbox.observeVisibleActions().collect { actions ->
                _state.update { it.copy(visibleRouteActions = actions) }
            }
        }
        viewModelScope.launch {
            networkMonitor.isOnline.collect { online ->
                liveSyncClient.onNetworkAvailable(online)
                _state.update {
                    it.copy(
                        isOnline = online,
                        isStale = if (!online && it.lastSuccessfulSyncAtEpochMillis != null) true else it.isStale,
                    )
                }
                if (online && repository.hasToken()) {
                    liveSyncClient.ensureConnected()
                    refreshCurrentScopeFromSyncState()
                }
            }
        }
        viewModelScope.launch {
            if (repository.hasToken()) {
                refreshRoutes()
            } else {
                _state.update { it.copy(screen = DriverScreen.Phone, loading = false) }
            }
        }
    }

    fun requestOtp(phone: String) = runLoading("request_otp") {
        val response = repository.requestOtp(phone)
        otpAutoSubmitPolicy.reset()
        _state.update {
            it.copy(
                screen = DriverScreen.Otp,
                phone = phone,
                otpCode = "",
                maskedPhone = response.phoneMasked.orEmpty(),
                error = null,
            )
        }
    }

    fun verifyOtp(code: String) = runLoading("verify_otp") {
        val driver = repository.verifyOtp(_state.value.phone, code, android.os.Build.MODEL ?: "Android")
        AppDiagnostics.setDriverId(driver.id)
        _state.update { it.copy(driver = driver, imageAuthHeader = repository.imageAuthHeader()) }
        refreshRoutes()
    }

    fun updateOtpCode(code: String) {
        val sanitized = code.filter(Char::isDigit).take(6)
        _state.update { it.copy(otpCode = sanitized) }

        if (otpAutoSubmitPolicy.shouldSubmit(sanitized, _state.value.loading)) {
            verifyOtp(sanitized)
        }
    }

    fun refreshRoutes() = loadRoutes(date = _state.value.selectedDate, showLoading = true, navigateToRoutes = true)

    fun refreshRoutesSilently() = loadRoutes(date = _state.value.selectedDate, showLoading = false, navigateToRoutes = false)

    fun onAppForegrounded() {
        liveSyncClient.ensureConnected()
        viewModelScope.launch { checkRemoteSyncState() }
        if (_state.value.screen == DriverScreen.Routes) {
            refreshRoutesSilently()
        }
    }

    fun selectRouteDate(date: String) {
        _state.update { it.copy(routeDayLiveUpdateMessage = null) }
        loadRoutes(date = date, showLoading = true, navigateToRoutes = true)
    }

    fun dismissRouteDayLiveUpdate() {
        _state.update { it.copy(routeDayLiveUpdateMessage = null) }
    }

    private fun loadRoutes(date: String?, showLoading: Boolean, navigateToRoutes: Boolean) {
        viewModelScope.launch {
            _state.update { it.copy(loading = showLoading, refreshing = !showLoading, feedback = null, error = null) }

            runCatching {
                val cached = syncRepository.bootstrap(date)
                val response = cached.value
                val settings = response.driverAppSettings
                AppDiagnostics.setDriverId(response.session.driver.id)
                _state.update {
                    it.copy(
                        driver = response.session.driver,
                        routes = response.routes.today,
                        dispatchSheetReminder = response.dispatchSheetReminder,
                        selectedDate = settings?.selectedDate ?: date ?: it.selectedDate,
                        minRouteDate = settings?.minDate ?: it.minRouteDate,
                        maxRouteDate = settings?.maxDate ?: it.maxRouteDate,
                        autoRefreshSeconds = settings?.autoRefreshSeconds ?: it.autoRefreshSeconds,
                        allowGalleryUploads = settings?.allowGalleryUploads ?: it.allowGalleryUploads,
                        allowRouteCompletion = settings?.allowRouteCompletion ?: it.allowRouteCompletion,
                        requirePreciseLocationForPhotos = settings?.requirePreciseLocationForPhotos
                            ?: it.requirePreciseLocationForPhotos,
                        leaveRequestsConfig = settings?.leaveRequests?.let { config ->
                            LeaveRequestsConfig(enabled = config.enabled, types = config.types)
                        },
                        screen = if (
                            settings?.leaveRequests?.enabled != true &&
                            it.screen in setOf(DriverScreen.LeaveRequests, DriverScreen.LeaveRequestDetail, DriverScreen.AddLeaveRequest)
                        ) DriverScreen.Routes else if (navigateToRoutes) DriverScreen.Routes else it.screen,
                        notifyNewRoutes = response.notificationPreferences?.notifyNewRoutes ?: it.notifyNewRoutes,
                        imageAuthHeader = repository.imageAuthHeader(),
                        isStale = cached.stale || !it.isOnline,
                        lastSuccessfulSyncAtEpochMillis = cached.syncedAtEpochMillis ?: it.lastSuccessfulSyncAtEpochMillis,
                        error = null,
                    )
                }
                response.session.driver.id.let { driverId ->
                    liveSyncClient.start(driverId, response.realtime)
                    registerPushTokenIfAvailable(driverId)
                }
                observeDispatchSheetUploads(response.dispatchSheetReminder?.workDate)
            }.onFailure { throwable ->
                reportHandledException("load_routes", throwable, mapOf("date" to date))
                _state.update {
                    val apiError = ApiErrorMapper.map(throwable)
                    it.withApiError(throwable).copy(
                        screen = when {
                            apiError.kind == ApiErrorKind.Auth -> DriverScreen.Phone
                            it.screen == DriverScreen.Initializing -> DriverScreen.Routes
                            else -> it.screen
                        },
                    )
                }
            }

            _state.update {
                it.copy(loading = false, refreshing = false)
            }
        }
    }

    fun openRoute(routeId: String) = openRouteWithTarget(routeId, DriverScreen.Detail)

    private fun openRouteWithTarget(routeId: String, targetScreen: DriverScreen) = runLoading("open_route", mapOf("route_id" to routeId)) {
        val cached = syncRepository.route(routeId)
        val response = cached.value
        photoUploadOutbox.discardConfirmedServerPhotos(response.route)
        _state.update {
            it.copy(
                screen = targetScreen,
                selectedRoute = response.route,
                routeStageWeightText = when (targetScreen) {
                    DriverScreen.LoadingWeight -> response.route.loadingWeight?.toString().orEmpty()
                    DriverScreen.FinishRoute -> response.route.unloadingWeight?.toString().orEmpty()
                    else -> it.routeStageWeightText
                },
                feedback = null,
                isStale = cached.stale || !it.isOnline,
                lastSuccessfulSyncAtEpochMillis = cached.syncedAtEpochMillis ?: it.lastSuccessfulSyncAtEpochMillis,
                error = null,
            )
        }
        observePhotoUploads(routeId)
        observeRouteActions(routeId)
    }

    fun refreshSelectedRoute() {
        val routeId = _state.value.selectedRoute?.id ?: return

        viewModelScope.launch {
            _state.update { it.copy(refreshing = true, feedback = null, error = null) }

            runCatching { syncRepository.route(routeId) }
                .onSuccess { cached ->
                    val response = cached.value
                    photoUploadOutbox.discardConfirmedServerPhotos(response.route)
                    _state.update {
                        it.copy(
                            selectedRoute = response.route,
                            routes = it.routes.map { route -> if (route.id == routeId) response.route else route },
                            isStale = cached.stale || !it.isOnline,
                            lastSuccessfulSyncAtEpochMillis = cached.syncedAtEpochMillis ?: it.lastSuccessfulSyncAtEpochMillis,
                            error = null,
                        )
                    }
                }
                .onFailure { throwable ->
                    _state.update { it.withApiError(throwable) }
                }

            _state.update { it.copy(refreshing = false) }
        }
    }

    fun openProfile() {
        _state.update { it.copy(screen = DriverScreen.Profile, error = null) }
    }

    fun updateNewRouteNotifications(enabled: Boolean) {
        viewModelScope.launch {
            _state.update { it.copy(refreshing = true, error = null, notificationPermissionDenied = false) }
            runCatching { repository.updateNotificationPreferences(enabled) }
                .onSuccess { preferences ->
                    _state.update {
                        it.copy(
                            notifyNewRoutes = preferences.notifyNewRoutes,
                            feedback = if (preferences.notifyNewRoutes) "Powiadomienia o nowych kursach są włączone." else "Powiadomienia o nowych kursach są wyłączone.",
                            error = null,
                        )
                    }
                }
                .onFailure { throwable ->
                    _state.update { it.withApiError(throwable) }
                }
            _state.update { it.copy(refreshing = false) }
        }
    }

    fun markNotificationPermissionDenied() {
        _state.update {
            it.copy(
                notifyNewRoutes = false,
                notificationPermissionDenied = true,
                error = "Aby otrzymywać powiadomienia o nowych kursach, włącz powiadomienia w ustawieniach aplikacji.",
            )
        }
    }

    fun setThemeMode(themeMode: AppThemeMode) {
        _state.update { it.copy(themeMode = themeMode) }
        viewModelScope.launch {
            runCatching { appPreferencesStore.setThemeMode(themeMode) }
                .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
        }
    }

    fun setRouteProgressNotificationEnabled(enabled: Boolean) {
        _state.update { it.copy(routeProgressNotificationEnabled = enabled) }
        viewModelScope.launch {
            runCatching { appPreferencesStore.setRouteProgressNotificationEnabled(enabled) }
                .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
        }
    }

    fun openPhotoQueue() {
        _state.update { it.copy(screen = DriverScreen.PhotoQueue, error = null) }
    }

    fun openLeaveRequests() {
        if (!DriverLeaveRequestUiRules.isFeatureVisible(_state.value.leaveRequestsConfig)) return
        loadLeaveRequests(showLoading = true, navigateToList = true)
    }

    fun refreshLeaveRequests() {
        if (!DriverLeaveRequestUiRules.isFeatureVisible(_state.value.leaveRequestsConfig)) return
        loadLeaveRequests(showLoading = false, navigateToList = false)
    }

    private fun loadLeaveRequests(showLoading: Boolean, navigateToList: Boolean) {
        viewModelScope.launch {
            _state.update { it.copy(loading = showLoading, refreshing = !showLoading, error = null, feedback = null) }
            runCatching { repository.leaveRequests() }
                .onSuccess { requests ->
                    _state.update { it.withLoadedLeaveRequests(requests, navigateToList) }
                }
                .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
            _state.update { it.copy(loading = false, refreshing = false) }
        }
    }

    fun openLeaveRequest(id: String) {
        if (!DriverLeaveRequestUiRules.isFeatureVisible(_state.value.leaveRequestsConfig)) return
        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null, feedback = null) }
            runCatching { repository.leaveRequest(id) }
                .onSuccess { request ->
                    _state.update { it.copy(screen = DriverScreen.LeaveRequestDetail, selectedLeaveRequest = request, error = null) }
                }
                .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
            _state.update { it.copy(loading = false) }
        }
    }

    fun openAddLeaveRequest() {
        val config = _state.value.leaveRequestsConfig
        if (!DriverLeaveRequestUiRules.isFeatureVisible(config)) return
        val today = LocalDate.now().toString()
        _state.update {
            it.copy(
                screen = DriverScreen.LeaveCalendar,
                leaveRequestDateFrom = today,
                leaveRequestDateTo = today,
                leaveRequestType = config?.types?.firstOrNull() ?: "URLOP",
                leaveRequestNote = "",
                leaveCalendarEntries = emptyList(),
                leaveCalendarLoadedUntil = null,
                leaveCalendarHasSelection = false,
                leaveCalendarSelectionAnchor = null,
                error = null,
                feedback = null,
            )
        }
        loadMoreLeaveCalendarMonths(reset = true)
    }

    fun updateLeaveRequestDraft(dateFrom: String? = null, dateTo: String? = null, type: String? = null, note: String? = null) {
        _state.update {
            val nextFrom = dateFrom ?: it.leaveRequestDateFrom
            val nextTo = dateTo ?: it.leaveRequestDateTo
            it.copy(
                leaveRequestDateFrom = nextFrom,
                leaveRequestDateTo = if (nextTo < nextFrom) nextFrom else nextTo,
                leaveRequestType = type ?: it.leaveRequestType,
                leaveRequestNote = note ?: it.leaveRequestNote,
            )
        }
    }

    fun selectLeaveCalendarDate(date: String) {
        val selected = runCatching { LocalDate.parse(date) }.getOrNull() ?: return
        if (selected.isBefore(LocalDate.now())) return

        _state.update {
            val anchor = it.leaveCalendarSelectionAnchor
            if (!it.leaveCalendarHasSelection || anchor == null) {
                it.copy(
                    leaveRequestDateFrom = date,
                    leaveRequestDateTo = date,
                    leaveCalendarHasSelection = true,
                    leaveCalendarSelectionAnchor = date,
                    error = null,
                )
            } else {
                val start = LocalDate.parse(anchor)
                val from = minOf(start, selected).toString()
                val to = maxOf(start, selected).toString()
                it.copy(
                    leaveRequestDateFrom = from,
                    leaveRequestDateTo = to,
                    leaveCalendarHasSelection = true,
                    leaveCalendarSelectionAnchor = null,
                    error = null,
                )
            }
        }
    }

    fun continueAddLeaveRequestFromCalendar() {
        if (!_state.value.leaveCalendarHasSelection) {
            _state.update { it.copy(error = "Wybierz termin urlopu w kalendarzu.") }
            return
        }

        _state.update { it.copy(screen = DriverScreen.AddLeaveRequest, error = null, feedback = null) }
    }

    fun reopenLeaveCalendarFromDraft() {
        if (!DriverLeaveRequestUiRules.isFeatureVisible(_state.value.leaveRequestsConfig)) return

        _state.update {
            it.copy(
                screen = DriverScreen.LeaveCalendar,
                error = null,
                feedback = null,
            )
        }
        if (_state.value.leaveCalendarLoadedUntil == null) {
            loadMoreLeaveCalendarMonths(reset = true)
        }
    }

    fun loadMoreLeaveCalendarMonths(reset: Boolean = false) {
        val snapshot = _state.value
        if (!DriverLeaveRequestUiRules.isFeatureVisible(snapshot.leaveRequestsConfig) || snapshot.leaveCalendarLoading) return

        val from = if (reset || snapshot.leaveCalendarLoadedUntil == null) {
            LocalDate.now()
        } else {
            runCatching { LocalDate.parse(snapshot.leaveCalendarLoadedUntil).plusDays(1) }.getOrDefault(LocalDate.now())
        }
        val to = from.plusMonths(6).minusDays(1)

        viewModelScope.launch {
            _state.update {
                it.copy(
                    leaveCalendarLoading = true,
                    error = null,
                    leaveCalendarEntries = if (reset) emptyList() else it.leaveCalendarEntries,
                    leaveCalendarLoadedUntil = if (reset) null else it.leaveCalendarLoadedUntil,
                )
            }
            runCatching { repository.leaveCalendar(from.toString(), to.toString()) }
                .onSuccess { entries ->
                    _state.update {
                        val merged = (it.leaveCalendarEntries + entries)
                            .distinctBy { entry -> entry.id }
                            .sortedWith(compareBy<DriverLeaveCalendarEntryDto> { entry -> entry.dateFrom.orEmpty() }.thenBy { entry -> entry.id })
                        it.copy(
                            leaveCalendarEntries = merged,
                            leaveCalendarLoadedUntil = to.toString(),
                            leaveCalendarLoading = false,
                            error = null,
                        )
                    }
                }
                .onFailure { throwable ->
                    _state.update { it.withApiError(throwable).copy(leaveCalendarLoading = false) }
                }
        }
    }

    fun submitLeaveRequest() {
        val snapshot = _state.value
        if (!DriverLeaveRequestUiRules.isFeatureVisible(snapshot.leaveRequestsConfig)) return
        if (snapshot.leaveRequestDateFrom < LocalDate.now().toString()) {
            _state.update { it.copy(error = "Data od nie może być z przeszłości.") }
            return
        }

        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null, feedback = null) }
            runCatching {
                repository.createLeaveRequest(
                    dateFrom = snapshot.leaveRequestDateFrom,
                    dateTo = snapshot.leaveRequestDateTo,
                    type = snapshot.leaveRequestType,
                    note = snapshot.leaveRequestNote.takeIf { it.isNotBlank() },
                )
            }.onSuccess { created ->
                val requests = runCatching { repository.leaveRequests() }.getOrElse { listOf(created) }
                _state.update {
                    it.copy(
                        screen = DriverScreen.LeaveRequestDetail,
                        selectedLeaveRequest = created,
                        leaveRequests = requests,
                        feedback = "Wniosek został wysłany do decyzji.",
                        error = null,
                    )
                }
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable) }
            }
            _state.update { it.copy(loading = false) }
        }
    }

    fun cancelSelectedLeaveRequest() {
        val request = _state.value.selectedLeaveRequest ?: return
        if (!DriverLeaveRequestUiRules.canCancel(request.status, request.dateFrom.orEmpty())) return

        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null, feedback = null) }
            runCatching { repository.cancelLeaveRequest(request.id) }
                .onSuccess { updated ->
                    _state.update {
                        it.copy(
                            selectedLeaveRequest = updated,
                            leaveRequests = it.leaveRequests.map { existing -> if (existing.id == updated.id) updated else existing },
                            feedback = if (updated.status == "cancel_requested") "Anulowanie wysłane do decyzji." else "Wniosek został anulowany.",
                            error = null,
                        )
                    }
                }
                .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
            _state.update { it.copy(loading = false) }
        }
    }

    fun uploadPhoto(uri: Uri, source: String, metadata: PhotoUploadMetadata = PhotoUploadMetadata(), stage: String = "other") {
        val route = _state.value.selectedRoute ?: return
        viewModelScope.launch {
            _state.update { it.copy(error = null) }
            runCatching { photoUploadOutbox.enqueue(route.id, uri, source, metadata, stage) }
                .onFailure { throwable ->
                    _state.update { it.withApiError(throwable) }
                }
        }
    }

    fun openStartRoute() {
        val route = _state.value.selectedRoute ?: return
        _state.update {
            it.copy(
                screen = DriverScreen.StartRoute,
                routeStageWeightText = "",
                feedback = null,
                error = null,
            )
        }
    }

    fun openLoadingWeight() {
        val snapshot = _state.value
        val route = snapshot.selectedRoute ?: return
        val stagePhotos = routePhotosForStage(route.photos, "loading")
        val stageUploads = snapshot.photoUploads.filter { normalizedRoutePhotoStage(it.stage) == "loading" }
        val blocker = loadingPhotosSubmitBlocker(
            serverPhotoCount = stagePhotos.size,
            localUploadCount = stageUploads.size,
        )
        if (blocker != null) {
            _state.update { it.copy(error = blocker) }
            return
        }

        _state.update {
            it.copy(
                screen = DriverScreen.LoadingWeight,
                routeStageWeightText = route.loadingWeight?.toString().orEmpty(),
                feedback = null,
                error = null,
            )
        }
    }

    fun openFinishRoute() {
        val route = _state.value.selectedRoute ?: return
        _state.update {
            it.copy(
                screen = DriverScreen.FinishRoute,
                routeStageWeightText = route.unloadingWeight?.toString().orEmpty(),
                feedback = null,
                error = null,
            )
        }
    }

    fun updateRouteStageWeight(value: String) {
        val normalized = value.filter { it.isDigit() || it == ',' || it == '.' }.take(8)
        _state.update { it.copy(routeStageWeightText = normalized) }
    }

    fun submitStartRoute() {
        submitRouteStage(stage = "loading")
    }

    fun submitFinishRoute() {
        submitRouteStage(stage = "unloading")
    }

    private fun submitRouteStage(stage: String) {
        val snapshot = _state.value
        val route = snapshot.selectedRoute ?: return
        val driver = snapshot.driver
        val weight = snapshot.routeStageWeightText.trim().replace(',', '.').toDoubleOrNull()
        val stagePhotos = routePhotosForStage(route.photos, stage)
        val stageUploads = snapshot.photoUploads.filter { normalizedRoutePhotoStage(it.stage) == stage }
        val submitBlocker = if (stage == "loading") {
            loadingWeightSubmitBlocker(
                weightText = snapshot.routeStageWeightText,
                serverPhotoCount = stagePhotos.size,
                localUploadCount = stageUploads.size,
            )
        } else {
            routeStageSubmitBlocker(
                weightText = snapshot.routeStageWeightText,
                serverPhotoCount = stagePhotos.size,
                localUploadCount = stageUploads.size,
            )
        }

        if (weight == null || submitBlocker != null) {
            _state.update { it.copy(error = submitBlocker ?: "Podaj poprawną wagę.") }
            return
        }

        val photoClientRequestIds = (stagePhotos.mapNotNull { it.clientRequestId } + stageUploads.map { it.clientRequestId })
            .map { it.trim() }
            .filter { it.isNotBlank() }
            .distinct()

        if (photoClientRequestIds.isEmpty()) {
            _state.update { it.copy(error = "Zdjęcie musi mieć lokalny identyfikator synchronizacji.") }
            return
        }

        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null, feedback = null) }
            runCatching {
                val updatedRoute = when (stage) {
                    "loading" -> {
                        routeActionOutbox.enqueueStart(route.id, weight, photoClientRequestIds)
                        route.copy(
                            status = "W TRAKCIE",
                            loadingWeight = weight,
                            trackingStatus = "active",
                            photos = route.safePhotos(),
                        )
                    }
                    "unloading" -> {
                        routeActionOutbox.enqueueFinish(route.id, weight, photoClientRequestIds)
                        route.copy(
                            status = "ZAKOŃCZONA",
                            unloadingWeight = weight,
                            trackingStatus = "finished",
                            completedAt = Instant.now().toString(),
                            photos = route.safePhotos(),
                        )
                    }
                    else -> route
                }

                syncRepository.cacheConfirmedRoute(updatedRoute, snapshot.selectedDate)
                _state.update {
                    it.copy(
                        screen = DriverScreen.Detail,
                        selectedRoute = updatedRoute,
                        routes = it.routes.map { item -> if (item.id == route.id) updatedRoute else item },
                        routeStageWeightText = "",
                        feedback = if (stage == "loading") {
                            "Kurs rozpoczęty lokalnie. Synchronizacja pójdzie w tle."
                        } else {
                            "Kurs zakończony lokalnie. Synchronizacja pójdzie w tle."
                        },
                        error = null,
                    )
                }

                if (stage == "loading" && driver != null) {
                    ActiveRouteTrackingService.start(
                        context = getApplication(),
                        route = updatedRoute,
                        driver = driver,
                        progressEnabled = snapshot.routeProgressNotificationEnabled,
                    )
                }
                if (stage == "unloading") {
                    ActiveRouteTrackingService.stop(getApplication())
                }
            }.onFailure { throwable ->
                reportHandledException("submit_route_stage", throwable, mapOf("route_id" to route.id, "stage" to stage))
                _state.update { it.withApiError(throwable) }
            }
            _state.update { it.copy(loading = false) }
        }
    }

    fun uploadDispatchSheetPhoto(uri: Uri, source: String, metadata: PhotoUploadMetadata = PhotoUploadMetadata()) {
        val reminder = _state.value.dispatchSheetReminder ?: return
        val workDate = reminder.workDate ?: return
        if (!reminder.canUpload) return

        viewModelScope.launch {
            _state.update { it.copy(error = null) }
            runCatching {
                dispatchSheetUploadOutbox.enqueue(
                    workDate = workDate,
                    replacePhotoId = reminder.photo?.id,
                    uri = uri,
                    source = source,
                    metadata = metadata,
                )
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable) }
            }
        }
    }

    fun retryPhotoUpload(upload: PhotoUploadEntity) {
        viewModelScope.launch {
            runCatching { photoUploadOutbox.retry(upload.clientRequestId) }
                .onFailure { throwable ->
                    _state.update { it.withApiError(throwable) }
                }
        }
    }

    fun deletePhoto(photo: RoutePhotoDto) {
        val route = _state.value.selectedRoute ?: return
        viewModelScope.launch {
            _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds + photo.id, error = null) }
            runCatching {
                repository.deletePhoto(photo.id)
                val response = syncRepository.route(route.id).value
                photoUploadOutbox.discardConfirmedServerPhotos(response.route)
                _state.update {
                    it.copy(
                        selectedRoute = response.route,
                        routes = it.routes.map { item -> if (item.id == route.id) response.route else item },
                        error = null,
                    )
                }
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable) }
            }
            _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds - photo.id) }
        }
    }

    fun deleteConfirmedUpload(upload: PhotoUploadEntity) {
        val route = _state.value.selectedRoute ?: return
        val serverPhotoId = confirmedUploadServerPhotoId(upload.status, upload.serverPhotoId) ?: return

        viewModelScope.launch {
            _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds + serverPhotoId, error = null) }
            runCatching {
                repository.deletePhoto(serverPhotoId)
                photoUploadOutbox.discard(upload.clientRequestId)
                val response = syncRepository.route(route.id).value
                photoUploadOutbox.discardConfirmedServerPhotos(response.route)
                _state.update {
                    it.copy(
                        selectedRoute = response.route,
                        routes = it.routes.map { item -> if (item.id == route.id) response.route else item },
                        error = null,
                    )
                }
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable) }
            }
            _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds - serverPhotoId) }
        }
    }

    fun completeSelectedRoute() {
        val route = _state.value.selectedRoute ?: return
        if (_state.value.completingRoute || !_state.value.isOnline) return

        viewModelScope.launch {
            _state.update { it.copy(completingRoute = true, feedback = null, error = null) }
            runCatching {
                val completed = repository.completeRoute(route.id).route
                syncRepository.cacheConfirmedRoute(completed, _state.value.selectedDate)
                val refreshed = runCatching { syncRepository.route(route.id) }
                    .getOrNull()
                    ?.takeUnless { it.stale }
                    ?.value
                    ?.route
                    ?: completed
                photoUploadOutbox.discardConfirmedServerPhotos(refreshed)
                _state.update {
                    it.copy(
                        selectedRoute = refreshed,
                        routes = it.routes.map { item -> if (item.id == route.id) refreshed else item },
                        feedback = "Kurs zakończony",
                        error = null,
                    )
                }
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable).copy(feedback = null) }
            }
            _state.update { it.copy(completingRoute = false) }
        }
    }

    fun openPhoto(photo: RoutePhotoDto) {
        _state.update { it.copy(screen = DriverScreen.Photo, selectedPhoto = photo) }
    }

    fun openRouteFromNotification(routeId: String?, target: String? = null) {
        if (routeId.isNullOrBlank()) return
        val targetScreen = if (target == "finish") DriverScreen.FinishRoute else DriverScreen.Detail
        openRouteWithTarget(routeId, targetScreen)
    }

    fun back() {
        _state.update {
            when (it.screen) {
                DriverScreen.Photo -> it.copy(screen = DriverScreen.Detail, selectedPhoto = null)
                DriverScreen.PhotoQueue -> it.copy(screen = DriverScreen.Routes)
                DriverScreen.StartRoute -> it.copy(screen = DriverScreen.Detail, routeStageWeightText = "", feedback = null)
                DriverScreen.LoadingWeight -> it.copy(screen = DriverScreen.StartRoute, feedback = null)
                DriverScreen.FinishRoute -> it.copy(screen = DriverScreen.Detail, routeStageWeightText = "", feedback = null)
                DriverScreen.LeaveRequests -> it.copy(screen = DriverScreen.Routes)
                DriverScreen.LeaveRequestDetail -> it.copy(screen = DriverScreen.LeaveRequests, selectedLeaveRequest = null, feedback = null)
                DriverScreen.LeaveCalendar -> it.copy(screen = DriverScreen.LeaveRequests, feedback = null)
                DriverScreen.AddLeaveRequest -> it.copy(screen = DriverScreen.LeaveRequests, feedback = null)
                DriverScreen.Detail -> {
                    photoUploadsJob?.cancel()
                    routeActionsJob?.cancel()
                    it.copy(screen = DriverScreen.Routes, selectedRoute = null, routeActions = emptyList(), photoUploads = emptyList(), feedback = null)
                }
                DriverScreen.Profile -> it.copy(screen = DriverScreen.Routes)
                DriverScreen.Otp -> it.copy(screen = DriverScreen.Phone)
                else -> it
            }
        }
    }

    fun logout() = runLoading("logout") {
        photoUploadsJob?.cancel()
        routeActionsJob?.cancel()
        dispatchSheetUploadsJob?.cancel()
        liveSyncClient.stop()
        repository.logout()
        syncRepository.clearCache()
        pushTokenRegisteredForDriverId = null
        AppDiagnostics.clearDriverId()
        _state.update { DriverUiState(screen = DriverScreen.Phone, loading = false) }
    }

    private fun runLoading(
        operation: String,
        keys: Map<String, Any?> = emptyMap(),
        block: suspend () -> Unit,
    ) {
        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null) }
            runCatching { block() }
                .onFailure { throwable ->
                    reportHandledException(operation, throwable, keys)
                    _state.update { it.withApiError(throwable) }
                }
            _state.update { it.copy(loading = false) }
        }
    }

    private fun observePhotoUploads(routeId: String) {
        photoUploadsJob?.cancel()
        photoUploadsJob = viewModelScope.launch {
            photoUploadOutbox.observeVisibleForRoute(routeId).collect { uploads ->
                _state.update { it.copy(photoUploads = uploads) }
            }
        }
    }

    private fun observeRouteActions(routeId: String) {
        routeActionsJob?.cancel()
        routeActionsJob = viewModelScope.launch {
            routeActionOutbox.observeUnresolvedForRoute(routeId).collect { actions ->
                _state.update { it.copy(routeActions = actions) }
            }
        }
    }

    private fun observeDispatchSheetUploads(workDate: String?) {
        dispatchSheetUploadsJob?.cancel()
        if (workDate.isNullOrBlank()) {
            _state.update { it.copy(dispatchSheetUploads = emptyList()) }
            return
        }

        dispatchSheetUploadsJob = viewModelScope.launch {
            dispatchSheetUploadOutbox.observeVisibleForWorkDate(workDate).collect { uploads ->
                _state.update { it.copy(dispatchSheetUploads = uploads) }
            }
        }
    }

    private fun refreshCurrentScopeFromSyncState() {
        val snapshot = _state.value
        if (snapshot.screen == DriverScreen.Routes) {
            refreshRoutesSilently()
            return
        }
        if (snapshot.screen == DriverScreen.Detail) {
            refreshSelectedRoute()
        }
        if (snapshot.screen == DriverScreen.LeaveRequests || snapshot.screen == DriverScreen.LeaveRequestDetail) {
            refreshLeaveRequests()
        }
    }

    private suspend fun checkRemoteSyncState() {
        val snapshot = _state.value
        val routeId = snapshot.selectedRoute?.id
        val remote = runCatching { syncRepository.fetchSyncState(snapshot.selectedDate, routeId) }.getOrNull() ?: return
        val needsRefresh = remote.scopes.any { syncRepository.shouldRefresh(it) }

        if (needsRefresh) {
            refreshCurrentScopeFromSyncState()
        } else {
            syncRepository.saveSyncStates(remote)
        }
    }

    private suspend fun handleSyncHint(hint: DriverSyncHint) {
        if (!syncRepository.shouldRefresh(hint.asScope())) return

        val snapshot = _state.value
        when (hint.scope) {
            DriverSyncRepository.SCOPE_ROUTES -> {
                if (shouldShowRouteDayLiveUpdate(snapshot.selectedDate, hint.date)) {
                    refreshRoutesSilently()
                    if (snapshot.screen == DriverScreen.Routes) {
                        _state.update {
                            it.copy(routeDayLiveUpdateMessage = "Spedytor zaktualizował zlecenia dla tego dnia.")
                        }
                    }
                } else {
                    DriverSyncWorker.enqueue(getApplication(), hint.date, null)
                }
            }
            DriverSyncRepository.SCOPE_ROUTE_DETAIL -> {
                if (hint.routeId == snapshot.selectedRoute?.id) {
                    refreshSelectedRoute()
                } else {
                    DriverSyncWorker.enqueue(getApplication(), hint.date, hint.routeId)
                }
            }
            DriverSyncRepository.SCOPE_SETTINGS -> refreshRoutesSilently()
            DriverSyncRepository.SCOPE_DISPATCH_SHEET -> refreshRoutesSilently()
            DriverSyncRepository.SCOPE_LEAVE_REQUESTS -> {
                if (snapshot.screen == DriverScreen.LeaveRequests || snapshot.screen == DriverScreen.LeaveRequestDetail) {
                    refreshLeaveRequests()
                }
            }
        }
    }

    private fun registerPushTokenIfAvailable(driverId: String) {
        if (pushTokenRegisteredForDriverId == driverId) return

        runCatching {
            FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
                viewModelScope.launch {
                    runCatching {
                        repository.storePushToken(token)
                        pushTokenRegisteredForDriverId = driverId
                    }.onFailure { throwable ->
                        reportHandledException("store_push_token", throwable, mapOf("driver_id" to driverId))
                    }
                }
            }.addOnFailureListener { throwable ->
                reportHandledException("get_push_token", throwable, mapOf("driver_id" to driverId))
            }
        }.onFailure { throwable ->
            reportHandledException("register_push_token", throwable, mapOf("driver_id" to driverId))
        }
    }

    private fun reportHandledException(
        operation: String,
        throwable: Throwable,
        keys: Map<String, Any?> = emptyMap(),
    ) {
        val apiError = ApiErrorMapper.map(throwable)
        if (!ApiErrorMapper.shouldReportNonFatal(throwable)) {
            AppDiagnostics.log("network_failure: $operation: ${throwable.message ?: throwable::class.java.simpleName}")
            return
        }
        AppDiagnostics.reportNonFatal(
            throwable = throwable,
            operation = operation,
            keys = mapOf(
                "screen" to _state.value.screen.name,
                "api_error_kind" to apiError.kind.name,
                "api_error_code" to apiError.code,
                "api_status_code" to apiError.statusCode,
                "retryable" to apiError.retryable,
            ) + keys,
        )
    }

    override fun onCleared() {
        liveSyncClient.close()
        photoUploadsJob?.cancel()
        routeActionsJob?.cancel()
        dispatchSheetUploadsJob?.cancel()
        super.onCleared()
    }
}
