package com.hermes.app.feature.session

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.hermes.app.R
import com.hermes.app.core.analytics.HermesAnalyticsTracker
import com.hermes.app.core.errors.mapUserFacingError
import com.hermes.app.core.localization.HermesLocalizationStore
import com.hermes.app.data.HermesRepository
import com.hermes.app.domain.HermesSessionStartRequest
import java.util.Locale
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
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.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime

data class SessionUiState(
    val isLoading: Boolean,
    val hasSession: Boolean,
    val statusText: String,
    val bannerMessage: String?,
    val sessionId: String?,
    val userId: String?,
    val localeCode: String,
    val startedAt: String?,
    val experimentVariant: String?,
    val appVersion: String?,
    val deviceModel: String?,
    val osVersion: String?,
)

class SessionViewModel(
    private val repository: HermesRepository,
    private val localizationStore: HermesLocalizationStore,
    private val analyticsTracker: HermesAnalyticsTracker,
    private val sessionRequestFactory: (String) -> HermesSessionStartRequest,
) : ViewModel() {
    private var bootstrapJob: Job? = null
    private var lastLocaleCode: String = localizationStore.localeCode.value

    val uiState: StateFlow<SessionUiState> = combine(
        repository.currentSession,
        repository.isLoading,
        repository.errorCause,
        localizationStore.localeCode,
    ) { session, isLoading, errorCause, localeCode ->
        val bannerMessage = mapUserFacingError(localizationStore, localeCode, errorCause)
        val statusText = when {
            session != null -> localizationStore.string(localeCode, R.string.session_status_ready)
            bannerMessage != null -> localizationStore.string(localeCode, R.string.session_status_error)
            else -> localizationStore.string(localeCode, R.string.session_status_loading)
        }

        SessionUiState(
            isLoading = isLoading && session == null,
            hasSession = session != null,
            statusText = statusText,
            bannerMessage = bannerMessage,
            sessionId = session?.sessionId?.toString(),
            userId = session?.userId?.toString(),
            localeCode = localeCode,
            startedAt = session?.startedAt?.formatCompact(),
            experimentVariant = session?.experimentVariant,
            appVersion = session?.appVersion,
            deviceModel = session?.deviceModel,
            osVersion = session?.osVersion,
        )
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.Eagerly,
        initialValue = SessionUiState(
            isLoading = true,
            hasSession = false,
            statusText = localizationStore.string(localizationStore.localeCode.value, R.string.session_status_loading),
            bannerMessage = null,
            sessionId = null,
            userId = null,
            localeCode = localizationStore.localeCode.value,
            startedAt = null,
            experimentVariant = null,
            appVersion = null,
            deviceModel = null,
            osVersion = null,
        ),
    )

    fun bootstrap(localeCode: String) {
        lastLocaleCode = localeCode

        if (bootstrapJob?.isActive == true) {
            return
        }

        if (repository.currentSession.value != null && repository.currentRound.value != null) {
            return
        }

        bootstrapJob = viewModelScope.launch {
            analyticsTracker.track(
                "session_start_requested",
                attributes = mapOf("screen_name" to "session", "locale_code" to localeCode),
            )

            try {
                repository.bootstrap(sessionRequestFactory(localeCode))
                analyticsTracker.track(
                    "session_started",
                    attributes = mapOf("screen_name" to "session", "locale_code" to localeCode),
                )
            } catch (error: Throwable) {
                if (error is CancellationException) {
                    return@launch
                }

                analyticsTracker.track(
                    "session_start_failed",
                    attributes = mapOf("screen_name" to "session", "locale_code" to localeCode),
                )
            }
        }
    }

    fun retryBootstrap() {
        bootstrapJob?.cancel()
        bootstrapJob = null
        bootstrap(lastLocaleCode)
    }

    private fun Instant.formatCompact(): String {
        val local = toLocalDateTime(TimeZone.currentSystemDefault())
        return String.format(
            Locale.US,
            "%04d-%02d-%02d %02d:%02d",
            local.year,
            local.monthNumber,
            local.dayOfMonth,
            local.hour,
            local.minute,
        )
    }
}
