package com.github.nullptroma.wallenc.app.sync

import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncPaths
import com.github.nullptroma.wallenc.domain.interfaces.IStorageSyncGroupStore
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import com.github.nullptroma.wallenc.domain.tasks.StorageSyncTriggerReason
import com.github.nullptroma.wallenc.usecases.RunStorageSyncUseCase
import com.github.nullptroma.wallenc.usecases.StorageSyncReadiness
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
@OptIn(FlowPreview::class)
class StorageSyncBootstrap @Inject constructor(
    private val scheduler: StorageSyncScheduler,
    private val vaultsManager: IVaultsManager,
    private val syncRunner: RunStorageSyncUseCase,
    private val syncReadiness: StorageSyncReadiness,
    private val groupStore: IStorageSyncGroupStore,
) {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private val startupSyncScheduled = AtomicBoolean(false)

    fun start() {
        scheduler.ensureScheduled()
        scheduleStartupSyncOnce()
        scope.launch {
            combine(
                vaultsManager.allStorages,
                vaultsManager.unlockManager.openedStorages,
            ) { rootStorages, opened ->
                (rootStorages + opened.values).distinctBy { it.uuid }
            }.collectLatest { storages ->
                if (storages.isEmpty()) {
                    return@collectLatest
                }
                val triggers = storages.flatMap { storage ->
                    listOf(
                        storage.accessor.filesUpdates
                            .filter { page ->
                                page.data.any { file ->
                                    StorageSyncPaths.isSyncableUserPath(file.metaInfo.path)
                                }
                            }
                            .map {},
                        storage.accessor.dirsUpdates
                            .filter { page ->
                                page.data.any { dir ->
                                    StorageSyncPaths.isSyncableUserPath(dir.metaInfo.path)
                                }
                            }
                            .map {},
                    )
                }
                merge(*triggers.toTypedArray())
                    .filter { shouldScheduleDebounceSync() }
                    .debounce(RunStorageSyncUseCase.DEBOUNCE_AFTER_CHANGE_MS)
                    .collect {
                        syncRunner.enqueue(StorageSyncTriggerReason.Debounce)
                    }
            }
        }
    }

    /**
     * Игнорировать события во время sync и сразу после него: запись файлов при sync
     * (в т.ч. через [EncryptedStorageAccessor]) иначе снова запускает debounce через 60 с.
     */
    private fun shouldScheduleDebounceSync(): Boolean {
        if (syncRunner.syncRunning.value) {
            return false
        }
        return System.currentTimeMillis() >= syncRunner.debounceSuppressUntilMs.value
    }

    /**
     * Одна синхронизация после готовности хранилищ при старте процесса — не ждать только WorkManager
     * (особенно если periodic work откладывался из‑за перезапусков процесса).
     */
    private fun scheduleStartupSyncOnce() {
        scope.launch {
            combine(
                vaultsManager.allStorages,
                vaultsManager.unlockManager.openedStorages,
            ) { rootStorages, opened ->
                (rootStorages + opened.values).distinctBy { it.uuid }
            }
                .map { it.isNotEmpty() }
                .distinctUntilChanged()
                .filter { it }
                .first()
            if (!startupSyncScheduled.compareAndSet(false, true)) {
                return@launch
            }
            if (groupStore.getGroups().isEmpty()) {
                return@launch
            }
            syncReadiness.awaitReady()
            syncRunner.enqueue(StorageSyncTriggerReason.Background)
        }
    }
}
