package hu.bbara.purefin.core.player.manager

import dagger.hilt.android.scopes.ViewModelScoped
import hu.bbara.purefin.core.data.MediaMetadataUpdater
import hu.bbara.purefin.core.data.PlaybackProgressReporter
import hu.bbara.purefin.core.data.PlaybackReportContext
import hu.bbara.purefin.core.player.model.MetadataState
import hu.bbara.purefin.core.player.model.PlaybackProgressSnapshot
import hu.bbara.purefin.core.player.model.PlaybackStateSnapshot
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject

@ViewModelScoped
class ProgressManager @Inject constructor(
    private val playbackProgressReporter: PlaybackProgressReporter,
    private val mediaMetadataUpdater: MediaMetadataUpdater,
) {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
    private var progressJob: Job? = null
    private var activeItemId: UUID? = null
    private var activePlaybackReportContext: PlaybackReportContext? = null
    private var lastPositionMs: Long = 0L
    private var lastDurationMs: Long = 0L
    private var isPaused: Boolean = false

    fun bind(
        playbackState: StateFlow<PlaybackStateSnapshot>,
        progress: StateFlow<PlaybackProgressSnapshot>,
        metadata: StateFlow<MetadataState>
    ) {
        scope.launch {
            combine(playbackState, progress, metadata) { state, prog, meta ->
                Triple(state, prog, meta)
            }.collect { (state, prog, meta) ->
                isPaused = !state.isPlaying
                val mediaId = meta.mediaId?.let { runCatching { UUID.fromString(it) }.getOrNull() }
                val nextPlaybackReportContext = meta.playbackReportContext

                if (activeItemId != null && (mediaId != activeItemId || state.isEnded)) {
                    stopSession()
                }

                lastPositionMs = prog.positionMs
                lastDurationMs = prog.durationMs

                if (activeItemId == null && mediaId != null && !state.isEnded) {
                    startSession(mediaId, prog.positionMs, nextPlaybackReportContext)
                } else if (activeItemId == mediaId) {
                    activePlaybackReportContext = nextPlaybackReportContext
                }
            }
        }
    }

    private fun startSession(itemId: UUID, positionMs: Long, reportContext: PlaybackReportContext?) {
        activeItemId = itemId
        activePlaybackReportContext = reportContext
        report(itemId, positionMs, reportContext = reportContext, isStart = true)
        progressJob = scope.launch {
            while (isActive) {
                delay(5000)
                report(itemId, lastPositionMs, reportContext = activePlaybackReportContext, isPaused = isPaused)
            }
        }
    }

    private fun stopSession() {
        progressJob?.cancel()
        activeItemId?.let { itemId ->
            report(itemId, lastPositionMs, reportContext = activePlaybackReportContext, isStop = true)
            scope.launch(Dispatchers.IO) {
                try {
                    mediaMetadataUpdater.updateWatchProgress(itemId, lastPositionMs, lastDurationMs)
                } catch (e: Exception) {
                    Timber.tag(TAG).e(e, "Local cache update failed")
                }
            }
        }
        activeItemId = null
        activePlaybackReportContext = null
    }

    private fun report(
        itemId: UUID,
        positionMs: Long,
        reportContext: PlaybackReportContext?,
        isPaused: Boolean = false,
        isStart: Boolean = false,
        isStop: Boolean = false
    ) {
        val ticks = positionMs * 10_000
        scope.launch(Dispatchers.IO) {
            try {
                if (reportContext == null) {
                    return@launch
                }
                when {
                    isStart -> playbackProgressReporter.reportPlaybackStart(itemId, ticks, reportContext)
                    isStop -> playbackProgressReporter.reportPlaybackStopped(itemId, ticks, reportContext)
                    else -> playbackProgressReporter.reportPlaybackProgress(itemId, ticks, isPaused, reportContext)
                }
                Timber.tag(TAG).d("${if (isStart) "Start" else if (isStop) "Stop" else "Progress"}: $itemId at ${positionMs}ms, paused=$isPaused")
            } catch (e: Exception) {
                Timber.tag(TAG).e(e, "Report failed")
            }
        }
    }

    fun release() {
        progressJob?.cancel()
        activeItemId?.let { itemId ->
            val ticks = lastPositionMs * 10_000
            val posMs = lastPositionMs
            val durMs = lastDurationMs
            CoroutineScope(Dispatchers.IO).launch {
                try {
                    activePlaybackReportContext?.let { reportContext ->
                        playbackProgressReporter.reportPlaybackStopped(itemId, ticks, reportContext)
                    }
                    mediaMetadataUpdater.updateWatchProgress(itemId, posMs, durMs)
                    Timber.tag(TAG).d("Stop: $itemId at ${posMs}ms")
                } catch (e: Exception) {
                    Timber.tag(TAG).e(e, "Report failed")
                }
            }
        }
        scope.cancel()
    }

    private companion object {
        const val TAG = "ProgressManager"
    }
}
