package com.hermes.app.data

import com.hermes.app.core.network.HermesApiClient
import com.hermes.app.domain.HermesAnalyticsBatchRequest
import com.hermes.app.domain.HermesBetIntentRequest
import com.hermes.app.domain.HermesBetIntentResponse
import com.hermes.app.domain.HermesExperimentConfig
import com.hermes.app.domain.HermesLocalizationBundle
import com.hermes.app.domain.HermesMarket
import com.hermes.app.domain.HermesOddsVersion
import com.hermes.app.domain.HermesRound
import com.hermes.app.domain.HermesSessionResponse
import com.hermes.app.domain.HermesSessionStartRequest
import com.hermes.app.domain.HermesSettlement
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class HermesRepository(private val apiClient: HermesApiClient) {
    private val sessionMutex = Mutex()
    private val _currentSession = MutableStateFlow<HermesSessionResponse?>(null)
    private val _currentRound = MutableStateFlow<HermesRound?>(null)
    private val _isLoading = MutableStateFlow(true)
    private val _errorCause = MutableStateFlow<Throwable?>(null)
    private val _serverClockOffsetMs = MutableStateFlow<Long?>(null)

    val currentSession: StateFlow<HermesSessionResponse?> = _currentSession.asStateFlow()
    val currentRound: StateFlow<HermesRound?> = _currentRound.asStateFlow()
    val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
    val errorCause: StateFlow<Throwable?> = _errorCause.asStateFlow()
    val serverClockOffsetMs: StateFlow<Long?> = _serverClockOffsetMs.asStateFlow()

    fun observeRound(): Flow<HermesRound?> = currentRound

    fun observeFeed(): Flow<HermesRound?> = currentRound

    suspend fun bootstrap(request: HermesSessionStartRequest): HermesSessionResponse {
        return sessionMutex.withLock {
            _isLoading.value = true
            _errorCause.value = null

            try {
                try {
                    syncClock()
                } catch (_: Throwable) {
                    // Clock sync is best-effort.
                }
                val session = _currentSession.value ?: startSession(request)
                if (_currentRound.value == null) {
                    _currentRound.value = loadRoundFromNetwork()
                }

                session
            } catch (error: Throwable) {
                _errorCause.value = error
                throw error
            } finally {
                _isLoading.value = false
            }
        }
    }

    suspend fun refreshRoundFromNetwork(): HermesRound {
        return sessionMutex.withLock {
            _isLoading.value = true
            _errorCause.value = null

            try {
                try {
                    syncClock()
                } catch (_: Throwable) {
                    // Clock sync is best-effort.
                }
                val round = loadRoundFromNetwork()
                _currentRound.value = round
                round
            } catch (error: Throwable) {
                _errorCause.value = error
                throw error
            } finally {
                _isLoading.value = false
            }
        }
    }

    suspend fun startSession(request: HermesSessionStartRequest): HermesSessionResponse {
        val session = apiClient.startSession(request)
        _currentSession.value = session
        return session
    }

    suspend fun endSession(): HermesSessionResponse {
        val session = apiClient.endSession()
        _currentSession.value = session
        return session
    }

    suspend fun submitBetIntent(request: HermesBetIntentRequest): HermesBetIntentResponse {
        return apiClient.submitBetIntent(request)
    }

    suspend fun currentOdds(marketId: String): HermesOddsVersion {
        return apiClient.currentOdds(marketId)
    }

    suspend fun settlement(eventId: String): HermesSettlement {
        return apiClient.settlement(eventId)
    }

    suspend fun experimentConfig(): HermesExperimentConfig {
        return apiClient.experimentConfig()
    }

    suspend fun localization(localeCode: String): HermesLocalizationBundle {
        return apiClient.localization(localeCode)
    }

    suspend fun submitAnalyticsBatch(request: HermesAnalyticsBatchRequest) {
        apiClient.submitAnalyticsBatch(request)
    }

    fun serverNow(): Instant {
        val offsetMs = _serverClockOffsetMs.value ?: 0L
        return Instant.fromEpochMilliseconds(Clock.System.now().toEpochMilliseconds() + offsetMs)
    }

    private suspend fun syncClock() {
        val health = apiClient.health()
        _serverClockOffsetMs.value = health.serverTime.toEpochMilliseconds() - Clock.System.now().toEpochMilliseconds()
    }

    private suspend fun loadRoundFromNetwork(): HermesRound {
        val event = apiClient.nextEvent()
        val manifest = apiClient.eventManifest(event.id)
        val media = manifest.media.firstOrNull { it.mediaType == "hls_main" } ?: manifest.media.firstOrNull()
            ?: error("Event media not found")
        val market = manifest.markets.firstOrNull() ?: apiClient.markets(event.id).firstOrNull()
            ?: error("Event market not found")
        val oddsVersion = apiClient.currentOdds(market.id)
        val settlement = apiClient.settlement(event.id)

        return HermesRound(
            event = event,
            media = media,
            market = market,
            oddsVersion = oddsVersion,
            settlement = settlement,
        )
    }
}
