package com.isidroid.c23.ui.screen.render_preview

import android.util.SparseArray
import androidx.compose.ui.unit.IntSize
import androidx.core.net.toUri
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.isidroid.c23.constant.Argument
import com.isidroid.c23.domain.use_case.RenderUseCase
import com.isidroid.rendering.model.RenderResult
import com.isidroid.c23.ext.isDebug
import com.isidroid.c23.ext.renderPreviewDefaultSettings
import com.isidroid.core.FlowResult
import com.isidroid.core.vm.BaseViewModel
import com.isidroid.rendering.constant.PrintOrientation
import com.isidroid.rendering.constant.PrintSize
import com.isidroid.rendering.constant.orientationName
import com.isidroid.rendering.constant.printSizeName
import com.isidroid.spot.model.RichSpot
import com.isidroid.utils.catchTimber
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import javax.inject.Inject
import kotlin.random.Random

@HiltViewModel
class RenderViewModel @Inject constructor(
    private val useCase: RenderUseCase,
    savedState: SavedStateHandle
) : BaseViewModel<RenderContract.Event, RenderContract.State, RenderContract.Effect>() {
    private var _sourceList: List<File>? = null
    private var _containerSize: IntSize? = null
    private var _richSpot: RichSpot? = null
    private val _renderResults = SparseArray<RenderResult>()
    private val _renderResultsFlow = MutableStateFlow<SparseArray<RenderResult>?>(null)
    val renderResultsFlow = _renderResultsFlow.asStateFlow()

    init {
        viewModelScope.launch {
            savedState.getStateFlow<String?>(Argument.URI, null)
                .filterNotNull()
                .collect { uris -> prepareSourceFileList(uris) }
        }
    }

    override val isDebug: Boolean = isDebug()
    override fun setInitialState(): RenderContract.State = RenderContract.State(rendering = true)
    override suspend fun handleEvents(event: RenderContract.Event) {
        when (event) {
            RenderContract.Event.Click -> RenderContract.State()
            RenderContract.Event.OpenCopiesSettings -> setState { copy(openCopies = viewState.value.renderSettings.copies) }
            RenderContract.Event.OpenOrientationSettings -> setState { copy(openOrientation = viewState.value.renderSettings.orientation) }
            RenderContract.Event.OpenPrintSizeSettings -> setState { copy(openPrintSize = viewState.value.renderSettings.printSize) }
            RenderContract.Event.ToBack -> goBack()
            RenderContract.Event.OpenMap -> openMap()
            RenderContract.Event.OpenListProfiles -> setState { copy(printProfilesSelector = _richSpot?.printProfiles) }
            RenderContract.Event.SendPrintJob -> sendPrintJob()

            is RenderContract.Event.OnPageOpened -> onPageOpened(event.page, event.containerSize)
            is RenderContract.Event.UpdateRenderSettings -> updateRenderSettings(
                size = event.size,
                orientation = event.orientation,
                increaseCopy = event.increaseCopy,
                page = event.page
            )

            is RenderContract.Event.ChangePrintProfile -> changePrintProfile(event.profileId, event.page)
        }
    }


    // events
    private suspend fun prepareSourceFileList(uris: String) {
        useCase.prepare(uris.split(",").map { it.trim().toUri() })
            .flowOn(Dispatchers.IO)
            .catchTimber { }
            .collect { res ->
                when (res) {
                    FlowResult.Loading -> setState { copy(rendering = true) }
                    is FlowResult.Success -> onSourceFileListReady(res.result)
                }
            }
    }

    private suspend fun onPageOpened(page: Int, containerSize: IntSize? = null) {
        if (containerSize != null)
            _containerSize = containerSize

        val size = _containerSize
        val cachedResult = _renderResults.get(page)

        if (cachedResult != null) {
            onRenderPage(cachedResult, page)
            return
        }

        val state = viewState.value
        useCase.render(
            sourceFile = _sourceList?.get(page),
            containerSize = size,
            printProfile = state.printProfile,
            renderSettings = state.renderSettings
        )
            .flowOn(Dispatchers.IO)
            .catchTimber { Timber.e(it) }
            .collect { flowResult ->
                when (flowResult) {
                    FlowResult.Loading -> {}
                    is FlowResult.Success -> onRenderPage(flowResult.result, page)
                }
            }
    }

    private suspend fun updateRenderSettings(
        @PrintSize size: Int?,
        @PrintOrientation orientation: Int?,
        increaseCopy: Boolean?,
        forceRefresh: Boolean = false,
        page: Int? = null
    ) {
        val refreshRender = (increaseCopy == null && viewState.value.openCopies == null) || forceRefresh
        updateRenderResults(refreshRender)

        val current = viewState.value.renderSettings
        val copiesCount = getNumberCopies(current.copies, increaseCopy)
        val openCopies = if (increaseCopy != null) copiesCount else null

        val renderSettings = current.copy(
            printSize = size ?: current.printSize,
            orientation = orientation ?: current.orientation,
            copies = copiesCount,
            greyscale = viewState.value.printProfile?.grayscale == true
        )

        setState {
            copy(
                openPrintSize = null,
                openOrientation = null,
                openCopies = openCopies,
                renderSettings = renderSettings,
            )
        }


        if (refreshRender) {
            onPageOpened(page ?: 0)
        }
    }

    private fun openMap() {
        setState { copy(spotHasNoPrintProfiles = false) }
        setEffect { RenderContract.Effect.Navigation.ToMap }
    }

    private fun goBack() {
        setState { copy(spotHasNoPrintProfiles = false) }
        setEffect { RenderContract.Effect.Navigation.ToBack }
    }

    private suspend fun changePrintProfile(profileId: String?, page: Int?) {
        val printProfile = profileId?.let { _richSpot?.printProfiles?.find { it.id == profileId } } ?: viewState.value.printProfile
        setState { copy(printProfilesSelector = null, printProfile = printProfile) }

        if (profileId != null)
            updateRenderSettings(increaseCopy = null, orientation = null, size = null, page = page)
    }

    private suspend fun sendPrintJob() {
        val state = viewState.value

        useCase.createJob(
            spotId = _richSpot?.spot?.id,
            profileId = state.printProfile?.id,
            copies = state.renderSettings.copies,
            printSize = state.renderSettings.printSize,
            orientation = state.renderSettings.orientation,
            sourceFiles = _sourceList
        )
            .flowOn(Dispatchers.IO)
            .catchTimber { setState { copy(error = it) } }
            .collect { res ->
                when (res) {
                    FlowResult.Loading -> setState { copy(disablePrintButton = true) }
                    is FlowResult.Success -> onPrintJobCreated()
                }
            }
    }

    // callbacks
    private suspend fun onSourceFileListReady(list: List<File>) {
        _sourceList = list

        useCase.loadSpot()
            .flowOn(Dispatchers.IO)
            .catchTimber { setState { copy(spotHasNoPrintProfiles = true) } }
            .collect { richSpot ->
                _richSpot = richSpot
                val printProfile = richSpot.printProfiles.first()

                setState {
                    copy(
                        richSpot = richSpot,
                        renderSettings = renderPreviewDefaultSettings(greyscale = printProfile.grayscale),
                        printProfile = printProfile,
                        pageCount = list.size,
                        rendering = false
                    )
                }
            }
    }

    private suspend fun onRenderPage(result: RenderResult, page: Int) {
        _renderResults[page] = result
        _renderResultsFlow.emit(_renderResults)

        setState { copy(renderSeed = Random.nextInt()) }
    }

    private fun getNumberCopies(current: Int, increaseCopy: Boolean?): Int {
        if (increaseCopy == null) return current
        return when {
            increaseCopy -> current + 1
            else -> current.let { if (it == 1) 1 else it - 1 }
        }
    }

    private fun updateRenderResults(isReset: Boolean) {
        if (isReset)
            _renderResults.clear()
    }

    private fun onPrintJobCreated(){
        setEffect { RenderContract.Effect.PrintJobCreated }
        setEffect { RenderContract.Effect.Navigation.ToSelectContent }
    }
}