package ru.fincode.tsudesk.feature.news.domain.usecase

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.fincode.tsudesk.core.network.model.NetworkResult
import ru.fincode.tsudesk.feature.news.domain.model.NewsFeedState
import ru.fincode.tsudesk.feature.news.domain.model.NewsItem
import ru.fincode.tsudesk.feature.news.domain.repository.NewsRepository
import javax.inject.Inject

class NewsInfinityFeedUseCase @Inject constructor(
    private val repo: NewsRepository
) {
    private val actions = MutableSharedFlow<Action>(
        replay = 0,
        extraBufferCapacity = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )

    private val _state = MutableStateFlow(NewsFeedState())
    val state: StateFlow<NewsFeedState> = _state.asStateFlow()

    fun attach(scope: CoroutineScope) {
        scope.launch {
            actions.collect { action ->
                when (action) {
                    Action.Refresh -> doRefresh()
                    Action.LoadNext -> doLoadNext()
                }
            }
        }
    }

    fun refresh() {
        actions.tryEmit(Action.Refresh)
    }

    fun loadNext() {
        actions.tryEmit(Action.LoadNext)
    }

    private suspend fun doRefresh() {
        _state.value =
            NewsFeedState(isLoading = true, nextPage = 1, endReached = false, lastError = null)
        loadPageIntoState(page = 1, replace = true)
    }

    private suspend fun doLoadNext() {
        val s = _state.value
        if (s.isLoading || s.endReached) return
        _state.update { it.copy(isLoading = true, lastError = null) }
        loadPageIntoState(page = s.nextPage, replace = false)
    }

    private suspend fun loadPageIntoState(page: Int, replace: Boolean) {
        repo.loadPage(page).collect { result ->
            when (result) {
                is NetworkResult.Success -> {
                    val incoming = result.data
                    _state.update { current ->
                        val merged = if (replace) {
                            incoming
                        } else {
                            mergeUnique(current.items, incoming)
                        }

                        current.copy(
                            items = merged,
                            isLoading = false,
                            nextPage = page + 1,
                            endReached = incoming.isEmpty(),
                            lastError = null
                        )
                    }
                }

                is NetworkResult.Error -> {
                    _state.update { current ->
                        current.copy(
                            isLoading = false,
                            lastError = result.error
                        )
                    }
                }
            }
        }
    }

    private fun mergeUnique(existing: List<NewsItem>, incoming: List<NewsItem>): List<NewsItem> {
        if (incoming.isEmpty()) return existing
        val seen = existing.asSequence().map { it.id }.toHashSet()
        val add = incoming.filter { seen.add(it.id) }
        return existing + add
    }

    private sealed interface Action {
        data object Refresh : Action
        data object LoadNext : Action
    }
}