package com.hermes.app.feature.round

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.core.media.HermesPlayerCoordinator
import com.hermes.app.data.HermesRepository
import com.hermes.app.domain.HermesOutcome
import com.hermes.app.domain.HermesBetIntentRequest
import com.hermes.app.domain.HermesRound
import com.hermes.app.feature.selection.SelectionOptionUi
import java.util.Locale
import java.util.UUID
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant

enum class RoundPhase {
    PREVIEW,
    LOCKED,
    REVEAL,
    RESULT,
}

data class RoundUiState(
    val title: String,
    val subtitle: String,
    val countdownLabel: String,
    val countdownText: String,
    val countdownWarning: Boolean,
    val oddsLabel: String,
    val oddsValue: String,
    val phase: RoundPhase,
    val phaseLabel: String,
    val selectionStatusText: String,
    val selectionLocked: Boolean,
    val selectionOptions: List<SelectionOptionUi>,
    val selectedOutcomeId: String?,
    val selectedOutcomeTitle: String,
    val winningOutcomeTitle: String,
    val didWin: Boolean,
    val videoUrl: String,
    val confirmTitle: String,
    val revealTitle: String,
    val revealSubtitle: String,
    val revealStatusText: String,
    val revealSelectionLabel: String,
    val revealContinueTitle: String,
    val resultTitle: String,
    val resultSubtitle: String,
    val resultSelectionLabel: String,
    val resultOutcomeLabel: String,
    val resultWinLabel: String,
    val resultLoseLabel: String,
    val resultNextRoundTitle: String,
    val retryText: String,
    val hasRound: Boolean,
    val isLoading: Boolean,
    val bannerMessage: String?,
    val isSubmittingSelection: Boolean,
)

private data class RoundUiInputs(
    val round: HermesRound?,
    val localeCode: String,
    val phase: RoundPhase,
    val selectedOutcomeId: String?,
    val isLoading: Boolean,
    val errorCause: Throwable?,
    val actionMessage: String?,
    val isSubmitting: Boolean,
)

class RoundViewModel(
    private val repository: HermesRepository,
    private val localizationStore: HermesLocalizationStore,
    private val analyticsTracker: HermesAnalyticsTracker,
    private val playerCoordinator: HermesPlayerCoordinator,
) : ViewModel() {
    private val roundFlow = repository.currentRound
    private val localeFlow = localizationStore.localeCode
    private val phaseFlow = MutableStateFlow(RoundPhase.PREVIEW)
    private val selectedOutcomeIdFlow = MutableStateFlow<String?>(null)
    private val actionMessageFlow = MutableStateFlow<String?>(null)
    private val isSubmittingSelectionFlow = MutableStateFlow(false)
    private val nowFlow = flow {
        while (true) {
            emit(repository.serverNow())
            delay(1_000)
        }
    }
    private var transitionJob: Job? = null

    private val uiInputsFlow = combine(roundFlow, localeFlow) { round, localeCode ->
        RoundUiInputs(
            round = round,
            localeCode = localeCode,
            phase = phaseFlow.value,
            selectedOutcomeId = selectedOutcomeIdFlow.value,
            isLoading = repository.isLoading.value,
            errorCause = repository.errorCause.value,
            actionMessage = actionMessageFlow.value,
            isSubmitting = isSubmittingSelectionFlow.value,
        )
    }
        .combine(phaseFlow) { inputs, phase -> inputs.copy(phase = phase) }
        .combine(selectedOutcomeIdFlow) { inputs, selectedOutcomeId -> inputs.copy(selectedOutcomeId = selectedOutcomeId) }
        .combine(repository.isLoading) { inputs, isLoading -> inputs.copy(isLoading = isLoading) }
        .combine(repository.errorCause) { inputs, errorCause -> inputs.copy(errorCause = errorCause) }
        .combine(actionMessageFlow) { inputs, actionMessage -> inputs.copy(actionMessage = actionMessage) }
        .combine(isSubmittingSelectionFlow) { inputs, isSubmitting -> inputs.copy(isSubmitting = isSubmitting) }

    val uiState: StateFlow<RoundUiState> = combine(uiInputsFlow, nowFlow) { inputs, now ->
        buildUiState(inputs, now)
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.Eagerly,
            initialValue = buildUiState(
            RoundUiInputs(
                round = roundFlow.value,
                localeCode = localeFlow.value,
                phase = phaseFlow.value,
                selectedOutcomeId = selectedOutcomeIdFlow.value,
                isLoading = repository.isLoading.value,
                errorCause = repository.errorCause.value,
                actionMessage = actionMessageFlow.value,
                isSubmitting = isSubmittingSelectionFlow.value,
            ),
            repository.serverNow(),
        ),
    )

    init {
        viewModelScope.launch {
            roundFlow
                .filterNotNull()
                .distinctUntilChangedBy { it.event.id }
                .collect { round ->
                    startPreview(round)
                }
        }
    }

    fun onSelectOutcome(outcomeId: String) {
        val round = roundFlow.value ?: return
        if (phaseFlow.value != RoundPhase.PREVIEW || isSubmittingSelectionFlow.value) {
            return
        }

        if (isTimerLocked(round, repository.serverNow())) {
            return
        }

        selectedOutcomeIdFlow.value = outcomeId
        analyticsTracker.track(
            "outcome_focused",
            attributes = mapOf("screen_name" to "round", "outcome_id" to outcomeId),
        )
        analyticsTracker.track(
            "outcome_selected",
            attributes = mapOf("screen_name" to "round", "outcome_id" to outcomeId),
        )
    }

    fun onConfirmSelection() {
        val round = roundFlow.value ?: return
        val selectedOutcomeId = selectedOutcomeIdFlow.value ?: return
        if (phaseFlow.value != RoundPhase.PREVIEW || isSubmittingSelectionFlow.value) {
            return
        }

        if (isTimerLocked(round, repository.serverNow())) {
            return
        }

        val session = repository.currentSession.value
        if (session == null) {
            actionMessageFlow.value = localized(localeFlow.value, R.string.errors_session_expired)
            return
        }

        isSubmittingSelectionFlow.value = true
        actionMessageFlow.value = null

        viewModelScope.launch {
            try {
                val response = repository.submitBetIntent(
                    HermesBetIntentRequest(
                        sessionId = session.sessionId,
                        eventId = round.event.id,
                        marketId = round.market.id,
                        outcomeId = selectedOutcomeId,
                        idempotencyKey = UUID.randomUUID().toString(),
                        clientSentAt = Clock.System.now(),
                    ),
                )

                if (!response.accepted) {
                    actionMessageFlow.value = localized(localeFlow.value, R.string.errors_generic)
                    phaseFlow.value = RoundPhase.PREVIEW
                    return@launch
                }

                analyticsTracker.track(
                    "selection_submitted",
                    attributes = baseSelectionAttributes(selectedOutcomeId),
                )
                analyticsTracker.track(
                    "selection_accepted",
                    attributes = baseSelectionAttributes(selectedOutcomeId),
                )
                analyticsTracker.track(
                    "market_locked",
                    attributes = baseSelectionAttributes(selectedOutcomeId) + ("lock_reason" to "manual_selection"),
                )

                phaseFlow.value = RoundPhase.LOCKED
                playerCoordinator.pause()

                transitionJob?.cancel()
                transitionJob = viewModelScope.launch {
                    delay(750)
                    if (phaseFlow.value == RoundPhase.LOCKED) {
                        phaseFlow.value = RoundPhase.REVEAL
                        analyticsTracker.track(
                            "reveal_started",
                            attributes = roundAnalyticsAttributes(round) + baseSelectionAttributes(selectedOutcomeId),
                        )
                    }
                }
            } catch (error: Throwable) {
                actionMessageFlow.value = mapUserFacingError(localizationStore, localeFlow.value, error)
                phaseFlow.value = RoundPhase.PREVIEW
            } finally {
                isSubmittingSelectionFlow.value = false
            }
        }
    }

    fun onRevealAcknowledged() {
        val round = roundFlow.value ?: return
        val selectedOutcomeId = selectedOutcomeIdFlow.value ?: return

        analyticsTracker.track(
            "reveal_completed",
            attributes = roundAnalyticsAttributes(round) + baseSelectionAttributes(selectedOutcomeId),
        )
        phaseFlow.value = RoundPhase.RESULT
        analyticsTracker.track(
            "result_viewed",
            attributes = roundAnalyticsAttributes(round) + baseSelectionAttributes(selectedOutcomeId) + mapOf(
                "outcome" to resolveWinningOutcomeTitle(round, localeFlow.value),
            ),
        )
    }

    fun onNextRound() {
        analyticsTracker.track(
            "next_round_requested",
            attributes = mapOf("screen_name" to "result"),
        )

        viewModelScope.launch {
            transitionJob?.cancel()
            actionMessageFlow.value = null
            runCatching { repository.refreshRoundFromNetwork() }
        }
    }

    private fun startPreview(round: HermesRound) {
        transitionJob?.cancel()
        phaseFlow.value = RoundPhase.PREVIEW
        selectedOutcomeIdFlow.value = null
        actionMessageFlow.value = null
        isSubmittingSelectionFlow.value = false
        playerCoordinator.preparePreview(round.media.hlsMasterUrl)
        playerCoordinator.player.seekTo(round.media.previewStartMs.toLong())

        analyticsTracker.track("round_loaded", attributes = roundAnalyticsAttributes(round))
        analyticsTracker.track("preview_started", attributes = roundAnalyticsAttributes(round))
        analyticsTracker.track("screen_viewed", attributes = mapOf("screen_name" to "round"))
    }

    private fun buildUiState(inputs: RoundUiInputs, now: Instant): RoundUiState {
        val round = inputs.round
        val localeCode = inputs.localeCode
        val phase = inputs.phase
        val selectedOutcomeId = inputs.selectedOutcomeId
        val isLoading = inputs.isLoading
        val errorCause = inputs.errorCause
        val actionMessage = inputs.actionMessage
        val isSubmitting = inputs.isSubmitting

        val bannerMessage = actionMessage ?: mapUserFacingError(localizationStore, localeCode, errorCause)
        val hasRound = round != null
        val loading = isLoading && !hasRound
        val countdownText = round?.let { formatCountdown(it.event.lockAt, now) } ?: "--:--"
        val countdownWarning = round != null && phase == RoundPhase.PREVIEW && !isTimerLocked(round, now) && (round.event.lockAt.toEpochMilliseconds() - now.toEpochMilliseconds()) <= 10_000
        val selectionLocked = !hasRound || phase != RoundPhase.PREVIEW || isTimerLocked(round, now) || isSubmitting
        val options = round?.let { selectionOptions(it, localeCode) } ?: emptyList()
        val selectedTitle = round?.let { resolveSelectedOutcomeTitle(it, localeCode, selectedOutcomeId) } ?: localized(localeCode, R.string.round_selection_prompt)
        val winningTitle = round?.let { resolveWinningOutcomeTitle(it, localeCode) } ?: localized(localeCode, R.string.round_selection_prompt)

        return RoundUiState(
            title = localized(localeCode, R.string.round_title),
            subtitle = localized(localeCode, R.string.round_subtitle),
            countdownLabel = localized(localeCode, R.string.round_countdown_label),
            countdownText = countdownText,
            countdownWarning = countdownWarning,
            oddsLabel = localized(localeCode, R.string.round_odds_label),
            oddsValue = round?.let { oddsSummary(it) } ?: "--",
            phase = phase,
            phaseLabel = when {
                loading -> localized(localeCode, R.string.common_loading)
                !hasRound && bannerMessage != null -> localized(localeCode, R.string.errors_generic)
                phase == RoundPhase.PREVIEW && round != null && isTimerLocked(round, now) -> localized(localeCode, R.string.round_locked_label)
                else -> phaseLabel(localeCode, phase, round?.let { isTimerLocked(it, now) } ?: false)
            },
            selectionStatusText = when {
                isSubmitting -> localized(localeCode, R.string.common_loading)
                !hasRound && loading -> localized(localeCode, R.string.common_loading)
                !hasRound && bannerMessage != null -> bannerMessage
                selectionLocked -> localized(localeCode, R.string.round_locked_label)
                else -> localized(localeCode, R.string.round_selection_prompt)
            },
            selectionLocked = selectionLocked,
            selectionOptions = options,
            selectedOutcomeId = selectedOutcomeId,
            selectedOutcomeTitle = selectedTitle,
            winningOutcomeTitle = winningTitle,
            didWin = round != null && selectedOutcomeId == round.settlement.winningOutcomeId,
            videoUrl = round?.media?.hlsMasterUrl.orEmpty(),
            confirmTitle = localized(localeCode, R.string.round_primary_cta),
            revealTitle = localized(localeCode, R.string.reveal_title),
            revealSubtitle = localized(localeCode, R.string.reveal_subtitle),
            revealStatusText = localized(localeCode, R.string.reveal_status),
            revealSelectionLabel = localized(localeCode, R.string.result_selection_label),
            revealContinueTitle = localized(localeCode, R.string.reveal_cta),
            resultTitle = localized(localeCode, R.string.result_title),
            resultSubtitle = localized(localeCode, R.string.result_subtitle),
            resultSelectionLabel = localized(localeCode, R.string.result_selection_label),
            resultOutcomeLabel = localized(localeCode, R.string.result_outcome_label),
            resultWinLabel = localized(localeCode, R.string.result_win),
            resultLoseLabel = localized(localeCode, R.string.result_lose),
            resultNextRoundTitle = localized(localeCode, R.string.result_next_round),
            retryText = localized(localeCode, R.string.common_retry),
            hasRound = hasRound,
            isLoading = loading,
            bannerMessage = bannerMessage,
            isSubmittingSelection = isSubmitting,
        )
    }

    private fun localized(localeCode: String, resId: Int): String {
        return localizationStore.string(localeCode, resId)
    }

    private fun selectionOptions(round: HermesRound, localeCode: String): List<SelectionOptionUi> {
        val oddsByOutcomeId = round.oddsVersion.odds.associateBy { it.outcomeId }

        return round.market.outcomes
            .sortedBy { it.sortOrder }
            .map { outcome ->
                SelectionOptionUi(
                    id = outcome.id,
                    title = outcomeTitle(localeCode, outcome),
                    subtitle = localized(localeCode, R.string.round_selection_prompt),
                    odds = oddsByOutcomeId[outcome.id]?.decimalOdds?.let { formatOdds(it) } ?: "--",
                )
            }
    }

    private fun outcomeTitle(localeCode: String, outcome: HermesOutcome): String {
        return when (outcome.labelKey) {
            "round.home" -> localized(localeCode, R.string.round_home)
            "round.away" -> localized(localeCode, R.string.round_away)
            else -> outcome.outcomeCode.replaceFirstChar { char -> char.uppercaseChar().toString() }
        }
    }

    private fun resolveSelectedOutcomeTitle(round: HermesRound, localeCode: String, selectedOutcomeId: String?): String {
        if (selectedOutcomeId == null) {
            return localized(localeCode, R.string.round_selection_prompt)
        }

        return round.market.outcomes.firstOrNull { it.id == selectedOutcomeId }?.let { outcomeTitle(localeCode, it) }
            ?: localized(localeCode, R.string.round_selection_prompt)
    }

    private fun resolveWinningOutcomeTitle(round: HermesRound, localeCode: String): String {
        return round.market.outcomes.firstOrNull { it.id == round.settlement.winningOutcomeId }?.let { outcomeTitle(localeCode, it) }
            ?: localized(localeCode, R.string.round_selection_prompt)
    }

    private fun oddsSummary(round: HermesRound): String {
        val oddsByOutcomeId = round.oddsVersion.odds.associateBy { it.outcomeId }
        return round.market.outcomes
            .sortedBy { it.sortOrder }
            .mapNotNull { outcome ->
                oddsByOutcomeId[outcome.id]?.decimalOdds?.let { formatOdds(it) }
            }
            .joinToString(" / ")
    }

    private fun formatOdds(value: Double): String {
        return String.format(Locale.US, "%.2f", value)
    }

    private fun formatCountdown(lockAt: Instant, now: Instant): String {
        val remainingMillis = (lockAt.toEpochMilliseconds() - now.toEpochMilliseconds()).coerceAtLeast(0)
        val totalSeconds = (remainingMillis / 1_000).toInt()
        val minutes = totalSeconds / 60
        val seconds = totalSeconds % 60
        return String.format(Locale.US, "%02d:%02d", minutes, seconds)
    }

    private fun phaseLabel(localeCode: String, phase: RoundPhase, isTimerLocked: Boolean): String {
        return when (phase) {
            RoundPhase.PREVIEW -> if (isTimerLocked) localized(localeCode, R.string.round_locked_label) else localized(localeCode, R.string.round_preview_label)
            RoundPhase.LOCKED -> localized(localeCode, R.string.round_locked_label)
            RoundPhase.REVEAL -> localized(localeCode, R.string.reveal_title)
            RoundPhase.RESULT -> localized(localeCode, R.string.result_title)
        }
    }

    private fun isTimerLocked(round: HermesRound?, now: Instant): Boolean {
        return round != null && now.toEpochMilliseconds() >= round.event.lockAt.toEpochMilliseconds()
    }

    private fun baseSelectionAttributes(outcomeId: String): Map<String, String> {
        return mapOf("screen_name" to "round", "outcome_id" to outcomeId)
    }

    private fun roundAnalyticsAttributes(round: HermesRound): Map<String, String> {
        return mapOf(
            "screen_name" to "round",
            "event_id" to round.event.id,
            "market_id" to round.market.id,
        )
    }
}
