package com.github.premnirmal.ticker.home

import android.app.Application
import android.os.Build
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.github.premnirmal.ticker.AppPreferences
import com.github.premnirmal.ticker.createTimeString
import com.github.premnirmal.ticker.model.AlarmScheduler
import com.github.premnirmal.ticker.model.StocksProvider
import com.github.premnirmal.ticker.network.CommitsProvider
import com.github.premnirmal.ticker.network.NewsProvider
import com.github.premnirmal.ticker.notifications.NotificationsHandler
import com.github.premnirmal.ticker.ui.AppMessaging
import com.github.premnirmal.ticker.widget.WidgetData
import com.github.premnirmal.ticker.widget.WidgetDataProvider
import com.github.premnirmal.tickerwidget.BuildConfig
import com.github.premnirmal.tickerwidget.R
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import javax.inject.Inject

@HiltViewModel
class HomeViewModel @Inject constructor(
    application: Application,
    private val stocksProvider: StocksProvider,
    private val appPreferences: AppPreferences,
    private val newsProvider: NewsProvider,
    private val widgetDataProvider: WidgetDataProvider,
    private val notificationsHandler: NotificationsHandler,
    private val appMessaging: AppMessaging,
    private val alarmScheduler: AlarmScheduler,
) : AndroidViewModel(application) {

    private val commitsProvider by lazy { CommitsProvider() }

    val fetchState: StateFlow<StocksProvider.FetchState>
        get() = stocksProvider.fetchState
    val nextFetch: Flow<String>
        get() = stocksProvider.nextFetchMs.map {
            val instant = Instant.ofEpochMilli(it)
            val time = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault())
            time.createTimeString()
        }

    val isRefreshing: StateFlow<Boolean>
        get() = _isRefreshing
    private val _isRefreshing = MutableStateFlow(false)

    val homeEvent: Flow<HomeEvent>
        get() = _homeEvent
    private val _homeEvent = MutableSharedFlow<HomeEvent>()

    val widgets: StateFlow<List<WidgetData>>
        get() = widgetDataProvider.widgetData
    val hasWidget: Flow<Boolean>
        get() = widgetDataProvider.hasWidget

    val hasHoldings: Boolean
        get() = stocksProvider.hasPositions()

    val showAlarmPermissionRequest: Boolean
        get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmScheduler.canScheduleExactAlarm()

    private var fetchJob: Job? = null

    init {
        initCaches()
        viewModelScope.launch { widgetDataProvider.refreshWidgetDataList() }
    }

    private fun initCaches() {
        newsProvider.initCache()
    }

    fun initNotifications() {
        notificationsHandler.initialize()
    }

    fun sendHomeEvent(event: HomeEvent) {
        viewModelScope.launch {
            _homeEvent.emit(event)
        }
    }

    val totalGainLoss: Flow<TotalGainLoss>
        get() = stocksProvider.portfolio.map { portfolio ->
            val totalHoldings = portfolio.filter { it.hasPositions() }.sumOf { quote ->
                quote.holdings().toDouble()
            }
            val totalHoldingsStr = appPreferences.selectedDecimalFormat.format(totalHoldings)
            var totalGain = 0.0f
            var totalLoss = 0.0f
            val quotesWithPositions = portfolio.filter { it.hasPositions() }
            for (quote in quotesWithPositions) {
                val gainLoss = quote.gainLoss()
                if (gainLoss > 0.0f) {
                    totalGain += gainLoss
                } else {
                    totalLoss += gainLoss
                }
            }
            val totalGainStr = "+" + appPreferences.selectedDecimalFormat.format(totalGain)
            val totalLossStr = if (totalLoss != 0.0f) {
                appPreferences.selectedDecimalFormat.format(totalLoss)
            } else {
                ""
            }
            TotalGainLoss(totalHoldingsStr, totalGainStr, totalLossStr)
        }

    fun checkShowTutorial() {
        val tutorialShown = appPreferences.tutorialShown()
        if (!tutorialShown) {
            showTutorial()
        }
    }

    fun showTutorial() {
        viewModelScope.launch {
            val title = getApplication<Application>().getString(R.string.how_to_title)
            val message = getApplication<Application>().getString(R.string.how_to)
            appMessaging.sendBottomSheet(title = title, message = message)
            appPreferences.setTutorialShown(true)
        }
    }

    fun checkShowWhatsNew() {
        if (appPreferences.getLastSavedVersionCode() < BuildConfig.VERSION_CODE) {
            showWhatsNew()
        }
    }

    fun showWhatsNew() {
        viewModelScope.launch {
            val whatsNewResult = commitsProvider.loadWhatsNew()
            val title = getApplication<Application>().getString(R.string.whats_new_in, BuildConfig.VERSION_NAME)
            val message = with(whatsNewResult) {
                if (wasSuccessful) {
                    appPreferences.saveVersionCode(BuildConfig.VERSION_CODE)
                    data.joinToString("\n\u25CF ", "\u25CF ")
                } else {
                    "${getApplication<Application>().getString(
                        R.string.error_fetching_whats_new
                    )}\n\n :( ${error.message.orEmpty()}"
                }
            }
            appMessaging.sendBottomSheet(title = title, message = message)
        }
    }

    fun refresh() {
        viewModelScope.launch {
            if (widgets.value.isEmpty()) {
                widgetDataProvider.refreshWidgetDataList()
            }
            _isRefreshing.value = true
            stocksProvider.fetch()
        }.invokeOnCompletion {
            _isRefreshing.value = false
        }
    }

    fun fetchPortfolioInRealTime() {
        fetchJob = viewModelScope.launch(Dispatchers.Default) {
            do {
                var isMarketOpen = false
                val result = stocksProvider.fetch(false)
                if (result.wasSuccessful) {
                    isMarketOpen = result.data.any { it.isMarketOpen }
                }
                delay(StocksProvider.DEFAULT_INTERVAL_MS)
            } while (result.wasSuccessful && isMarketOpen)
        }
    }

    fun stopRealTimeFetch() {
        fetchJob?.cancel()
    }

    data class TotalGainLoss(
        val holdings: String,
        val gain: String,
        val loss: String
    )
}
