package com.github.nullptroma.wallenc.usecases

import com.github.nullptroma.wallenc.domain.errors.WallencException
import com.github.nullptroma.wallenc.domain.errors.toWallencException
import com.github.nullptroma.wallenc.domain.interfaces.IStorageSyncEngine
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
import com.github.nullptroma.wallenc.domain.tasks.IStorageSyncTaskTitleFormatter
import com.github.nullptroma.wallenc.domain.tasks.StorageSyncTriggerReason
import com.github.nullptroma.wallenc.domain.tasks.TaskId
import com.github.nullptroma.wallenc.domain.tasks.TaskLogKey
import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel
import com.github.nullptroma.wallenc.domain.tasks.TaskProgressLabel
import com.github.nullptroma.wallenc.domain.tasks.TaskRunState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class RunStorageSyncUseCase @Inject constructor(
    private val orchestrator: ITaskOrchestrator,
    private val syncEngine: IStorageSyncEngine,
    private val syncReadiness: StorageSyncReadiness,
    private val taskTitleFormatter: IStorageSyncTaskTitleFormatter,
) {
    private val running = AtomicBoolean(false)

    private val _syncRunning = MutableStateFlow(false)
    val syncRunning: StateFlow<Boolean> = _syncRunning.asStateFlow()

    private val _activeSyncTaskId = MutableStateFlow<TaskId?>(null)
    val activeSyncTaskId: StateFlow<TaskId?> = _activeSyncTaskId.asStateFlow()

    /** Не реагировать на debounce до этого момента (мс с эпохи) после завершения sync. */
    private val _debounceSuppressUntilMs = MutableStateFlow(0L)
    val debounceSuppressUntilMs: StateFlow<Long> = _debounceSuppressUntilMs.asStateFlow()

    /**
     * @param reason источник запуска — заголовок задачи и лог пайплайна
     * @return false, если синхронизация уже в очереди или выполняется — новая задача не создана
     */
    fun enqueue(reason: StorageSyncTriggerReason): Boolean {
        if (!running.compareAndSet(false, true)) {
            return false
        }
        _syncRunning.value = true
        try {
            val taskId = orchestrator.enqueue(
                title = taskTitleFormatter.format(reason),
                dispatcher = Dispatchers.IO,
                work = { ctx ->
                    try {
                        executeSync(
                            reason = reason,
                            reportProgress = { fraction, label ->
                                ctx.ensureNotCancelled()
                                ctx.reportProgress(fraction, label)
                            },
                            log = { level, key -> ctx.log(level, key) },
                        )
                    } catch (e: Exception) {
                        ctx.fail(e.toWallencException())
                    } finally {
                        clearRunningState()
                    }
                },
            )
            _activeSyncTaskId.value = taskId
            return true
        } catch (t: Throwable) {
            clearRunningState()
            throw t
        }
    }

    /**
     * Ставит sync в пайплайн задач (как debounce / sync-tab) и ждёт завершения.
     * Для WorkManager и других фоновых запусков без отдельного «orphan»-лога.
     */
    suspend fun enqueueAndAwait(reason: StorageSyncTriggerReason): StorageSyncRunOutcome {
        if (!enqueue(reason)) {
            return StorageSyncRunOutcome.SkippedAlreadyRunning
        }
        val taskId = _activeSyncTaskId.value
            ?: return StorageSyncRunOutcome.Completed
        return try {
            withTimeout(SYNC_AWAIT_TIMEOUT_MS) {
                syncRunning.filter { !it }.first()
            }
            val state = orchestrator.pipelineState.value.tasks.find { it.id == taskId }?.state
            when (state) {
                is TaskRunState.Failed -> StorageSyncRunOutcome.Failed(state.error)
                TaskRunState.Cancelled -> StorageSyncRunOutcome.Cancelled
                else -> StorageSyncRunOutcome.Completed
            }
        } catch (_: TimeoutCancellationException) {
            orchestrator.cancel(taskId)
            clearRunningState()
            StorageSyncRunOutcome.Failed(
                WallencException.Unknown(
                    cause = IllegalStateException("Storage sync await timed out after ${SYNC_AWAIT_TIMEOUT_MS}ms"),
                ),
            )
        }
    }

    private suspend fun executeSync(
        reason: StorageSyncTriggerReason,
        reportProgress: suspend (fraction: Float?, label: TaskProgressLabel?) -> Unit,
        log: (TaskLogLevel, TaskLogKey) -> Unit,
    ) {
        try {
            syncReadiness.awaitReady()
            log(TaskLogLevel.Info, TaskLogKey.SyncStarted(reason))
            reportProgress(null, TaskProgressLabel.SyncStarted)
            try {
                syncEngine.syncAllGroups { fraction, label ->
                    reportProgress(fraction, label)
                }
                log(TaskLogLevel.Info, TaskLogKey.SyncFinished(reason))
                reportProgress(null, TaskProgressLabel.SyncCompleted)
            } catch (e: Exception) {
                val err = e.toWallencException()
                log(TaskLogLevel.Error, TaskLogKey.SyncFailed(err, reason))
                throw e
            }
        } finally {
            extendDebounceSuppress()
        }
    }

    private fun extendDebounceSuppress() {
        _debounceSuppressUntilMs.value = System.currentTimeMillis() + DEBOUNCE_AFTER_CHANGE_MS
    }

    private fun clearRunningState() {
        running.set(false)
        _syncRunning.value = false
        _activeSyncTaskId.value = null
    }

    companion object {
        /** Пауза после последнего изменения перед debounce-sync; же окно подавления после sync. */
        const val DEBOUNCE_AFTER_CHANGE_MS = 60_000L

        /** Максимальное ожидание фонового worker (WorkManager) — не блокировать periodic work навсегда. */
        private const val SYNC_AWAIT_TIMEOUT_MS = 25L * 60 * 1000
    }
}
