package com.rosetta.messenger.network.connection

import com.rosetta.messenger.data.MessageRepository
import com.rosetta.messenger.network.PacketSync
import com.rosetta.messenger.network.SyncStatus
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.launch

class SyncCoordinator(
    private val scope: CoroutineScope,
    private val syncRequestTimeoutMs: Long,
    private val manualSyncBacktrackMs: Long,
    private val addLog: (String) -> Unit,
    private val isAuthenticated: () -> Boolean,
    private val getRepository: () -> MessageRepository?,
    private val getProtocolPublicKey: () -> String,
    private val sendPacket: (PacketSync) -> Unit,
    private val onSyncCompleted: (String) -> Unit,
    private val whenInboundTasksFinish: suspend () -> Boolean
) {
    private val _syncInProgress = MutableStateFlow(false)
    val syncInProgress: StateFlow<Boolean> = _syncInProgress.asStateFlow()

    @Volatile private var syncBatchInProgress = false
    @Volatile private var syncRequestInFlight = false
    @Volatile private var resyncRequiredAfterAccountInit = false
    @Volatile private var lastForegroundSyncTime = 0L
    @Volatile private var syncRequestTimeoutJob: Job? = null

    private val inboundProcessingFailures = AtomicInteger(0)
    private val inboundTasksInCurrentBatch = AtomicInteger(0)
    private val fullFailureBatchStreak = AtomicInteger(0)
    private val syncBatchEndMutex = Mutex()

    fun isBatchInProgress(): Boolean = syncBatchInProgress

    fun isRequestInFlight(): Boolean = syncRequestInFlight

    fun markSyncInProgress(value: Boolean) {
        syncBatchInProgress = value
        if (_syncInProgress.value != value) {
            _syncInProgress.value = value
        }
    }

    fun clearRequestState() {
        syncRequestInFlight = false
        clearSyncRequestTimeout()
    }

    fun clearResyncRequired() {
        resyncRequiredAfterAccountInit = false
    }

    fun shouldResyncAfterAccountInit(): Boolean = resyncRequiredAfterAccountInit

    fun requireResyncAfterAccountInit(reason: String) {
        if (!resyncRequiredAfterAccountInit) {
            addLog(reason)
        }
        resyncRequiredAfterAccountInit = true
    }

    fun markInboundProcessingFailure() {
        inboundProcessingFailures.incrementAndGet()
    }

    fun trackInboundTaskQueued() {
        if (syncBatchInProgress) {
            inboundTasksInCurrentBatch.incrementAndGet()
        }
    }

    fun requestSynchronize() {
        if (syncBatchInProgress) {
            addLog("⚠️ SYNC request skipped: sync already in progress")
            return
        }
        if (syncRequestInFlight) {
            addLog("⚠️ SYNC request skipped: previous request still in flight")
            return
        }
        syncRequestInFlight = true
        addLog("🔄 SYNC requested — fetching last sync timestamp...")
        scope.launch {
            val repository = getRepository()
            if (repository == null || !repository.isInitialized()) {
                syncRequestInFlight = false
                clearSyncRequestTimeout()
                requireResyncAfterAccountInit("⏳ Sync postponed until account is initialized")
                return@launch
            }
            val protocolAccount = getProtocolPublicKey().trim()
            val repositoryAccount = repository.getCurrentAccountKey()?.trim().orEmpty()
            if (
                protocolAccount.isNotBlank() &&
                    repositoryAccount.isNotBlank() &&
                    !repositoryAccount.equals(protocolAccount, ignoreCase = true)
            ) {
                syncRequestInFlight = false
                clearSyncRequestTimeout()
                requireResyncAfterAccountInit(
                    "⏳ Sync postponed: repository bound to another account"
                )
                return@launch
            }
            val lastSync = repository.getLastSyncTimestamp()
            addLog("🔄 SYNC sending request with lastSync=$lastSync")
            sendSynchronize(lastSync)
        }
    }

    fun handleSyncPacket(packet: PacketSync) {
        syncRequestInFlight = false
        clearSyncRequestTimeout()
        when (packet.status) {
            SyncStatus.BATCH_START -> {
                addLog("🔄 SYNC BATCH_START — incoming message batch")
                markSyncInProgress(true)
                inboundProcessingFailures.set(0)
                inboundTasksInCurrentBatch.set(0)
            }

            SyncStatus.BATCH_END -> {
                addLog("🔄 SYNC BATCH_END — waiting for tasks to finish (ts=${packet.timestamp})")
                scope.launch {
                    syncBatchEndMutex.withLock {
                        val tasksFinished = whenInboundTasksFinish()
                        if (!tasksFinished) {
                            val fallbackCursor = getRepository()?.getLastSyncTimestamp() ?: 0L
                            sendSynchronize(fallbackCursor)
                            return@launch
                        }
                        val failuresInBatch = inboundProcessingFailures.getAndSet(0)
                        val tasksInBatch = inboundTasksInCurrentBatch.getAndSet(0)
                        val fullBatchFailure = tasksInBatch > 0 && failuresInBatch >= tasksInBatch
                        if (failuresInBatch > 0) {
                            addLog(
                                "⚠️ SYNC batch had $failuresInBatch processing error(s) out of $tasksInBatch task(s)"
                            )
                            if (fullBatchFailure) {
                                val streak = fullFailureBatchStreak.incrementAndGet()
                                val fallbackCursor = getRepository()?.getLastSyncTimestamp() ?: 0L
                                if (streak <= 2) {
                                    addLog(
                                        "🛟 SYNC full-batch failure ($failuresInBatch/$tasksInBatch), keeping cursor=$fallbackCursor and retrying batch (streak=$streak)"
                                    )
                                    sendSynchronize(fallbackCursor)
                                    return@launch
                                }
                                addLog(
                                    "⚠️ SYNC full-batch failure streak=$streak, advancing cursor to avoid deadlock"
                                )
                            } else {
                                fullFailureBatchStreak.set(0)
                            }
                        } else {
                            fullFailureBatchStreak.set(0)
                        }
                        getRepository()?.updateLastSyncTimestamp(packet.timestamp)
                        addLog("🔄 SYNC tasks done — cursor=${packet.timestamp}, requesting next batch")
                        sendSynchronize(packet.timestamp)
                    }
                }
            }

            SyncStatus.NOT_NEEDED -> {
                onSyncCompleted("✅ SYNC COMPLETE — no more messages to sync")
            }
        }
    }

    fun syncOnForeground() {
        if (!isAuthenticated()) return
        if (syncBatchInProgress) return
        if (syncRequestInFlight) return
        val now = System.currentTimeMillis()
        if (now - lastForegroundSyncTime < 5_000L) return
        lastForegroundSyncTime = now
        addLog("🔄 SYNC on foreground resume")
        requestSynchronize()
    }

    fun forceSynchronize(backtrackMs: Long = manualSyncBacktrackMs) {
        if (!isAuthenticated()) return
        if (syncBatchInProgress) return
        if (syncRequestInFlight) return

        scope.launch {
            val repository = getRepository()
            if (repository == null || !repository.isInitialized()) {
                requireResyncAfterAccountInit("⏳ Manual sync postponed until account is initialized")
                return@launch
            }
            val currentSync = repository.getLastSyncTimestamp()
            val rewindTo = (currentSync - backtrackMs.coerceAtLeast(0L)).coerceAtLeast(0L)

            syncRequestInFlight = true
            addLog("🔄 MANUAL SYNC requested: lastSync=$currentSync -> rewind=$rewindTo")
            sendSynchronize(rewindTo)
        }
    }

    fun onSyncCompletedStateApplied() {
        clearRequestState()
        inboundProcessingFailures.set(0)
        inboundTasksInCurrentBatch.set(0)
        fullFailureBatchStreak.set(0)
        markSyncInProgress(false)
    }

    fun resetForDisconnect() {
        clearRequestState()
        markSyncInProgress(false)
        clearResyncRequired()
        inboundProcessingFailures.set(0)
        inboundTasksInCurrentBatch.set(0)
        fullFailureBatchStreak.set(0)
    }

    private fun sendSynchronize(timestamp: Long) {
        syncRequestInFlight = true
        scheduleSyncRequestTimeout(timestamp)
        sendPacket(
            PacketSync().apply {
                status = SyncStatus.NOT_NEEDED
                this.timestamp = timestamp
            }
        )
    }

    private fun scheduleSyncRequestTimeout(cursor: Long) {
        syncRequestTimeoutJob?.cancel()
        syncRequestTimeoutJob =
            scope.launch {
                delay(syncRequestTimeoutMs)
                if (!syncRequestInFlight || !isAuthenticated()) return@launch
                syncRequestInFlight = false
                addLog("⏱️ SYNC response timeout for cursor=$cursor, retrying request")
                requestSynchronize()
            }
    }

    private fun clearSyncRequestTimeout() {
        syncRequestTimeoutJob?.cancel()
        syncRequestTimeoutJob = null
    }
}
