package com.rosetta.messenger.network.connection

import com.rosetta.messenger.network.PacketMessage
import com.rosetta.messenger.utils.RosettaDev1Log
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
 * Outgoing retry queue for PacketMessage delivery.
 *
 * Mirrors iOS behavior:
 * - retry every 4s,
 * - max 3 attempts,
 * - max 80s lifetime.
 */
class RetryQueueService(
    private val scope: CoroutineScope,
    private val sendPacket: (PacketMessage) -> Unit,
    private val isAuthenticated: () -> Boolean,
    private val addLog: (String) -> Unit,
    private val markOutgoingAsError: suspend (messageId: String, packet: PacketMessage) -> Unit,
    private val retryIntervalMs: Long = 4_000L,
    private val maxRetryAttempts: Int = 3,
    private val maxLifetimeMs: Long = 80_000L
) {
    private val pendingOutgoingPackets = ConcurrentHashMap<String, PacketMessage>()
    private val pendingOutgoingAttempts = ConcurrentHashMap<String, Int>()
    private val pendingOutgoingRetryJobs = ConcurrentHashMap<String, Job>()

    fun register(packet: PacketMessage) {
        val messageId = packet.messageId
        RosettaDev1Log.d("net/retry register msg=${messageId.take(8)}")
        pendingOutgoingRetryJobs[messageId]?.cancel()
        pendingOutgoingPackets[messageId] = packet
        pendingOutgoingAttempts[messageId] = 0
        schedule(messageId)
    }

    fun resolve(messageId: String) {
        RosettaDev1Log.d("net/retry resolve msg=${messageId.take(8)}")
        pendingOutgoingRetryJobs[messageId]?.cancel()
        pendingOutgoingRetryJobs.remove(messageId)
        pendingOutgoingPackets.remove(messageId)
        pendingOutgoingAttempts.remove(messageId)
    }

    fun clear() {
        RosettaDev1Log.d("net/retry clear size=${pendingOutgoingRetryJobs.size}")
        pendingOutgoingRetryJobs.values.forEach { it.cancel() }
        pendingOutgoingRetryJobs.clear()
        pendingOutgoingPackets.clear()
        pendingOutgoingAttempts.clear()
    }

    private fun schedule(messageId: String) {
        pendingOutgoingRetryJobs[messageId]?.cancel()
        pendingOutgoingRetryJobs[messageId] =
            scope.launch {
                delay(retryIntervalMs)

                val packet = pendingOutgoingPackets[messageId] ?: return@launch
                val attempts = pendingOutgoingAttempts[messageId] ?: 0

                val nowMs = System.currentTimeMillis()
                val ageMs = nowMs - packet.timestamp
                if (ageMs >= maxLifetimeMs) {
                    RosettaDev1Log.w(
                        "net/retry expired msg=${messageId.take(8)} age=${ageMs}ms"
                    )
                    addLog(
                        "⚠️ Message ${messageId.take(8)} expired after ${ageMs}ms — marking as error"
                    )
                    scope.launch { markOutgoingAsError(messageId, packet) }
                    resolve(messageId)
                    return@launch
                }

                if (attempts >= maxRetryAttempts) {
                    RosettaDev1Log.w(
                        "net/retry exhausted msg=${messageId.take(8)} attempts=$attempts"
                    )
                    addLog(
                        "⚠️ Message ${messageId.take(8)} exhausted $attempts retries — marking as error"
                    )
                    scope.launch { markOutgoingAsError(messageId, packet) }
                    resolve(messageId)
                    return@launch
                }

                if (!isAuthenticated()) {
                    RosettaDev1Log.w("net/retry deferred-not-auth msg=${messageId.take(8)}")
                    addLog("⏳ Message ${messageId.take(8)} retry deferred — not authenticated")
                    resolve(messageId)
                    return@launch
                }

                val nextAttempt = attempts + 1
                pendingOutgoingAttempts[messageId] = nextAttempt
                RosettaDev1Log.i(
                    "net/retry resend msg=${messageId.take(8)} attempt=$nextAttempt/$maxRetryAttempts"
                )
                addLog("🔄 Retrying message ${messageId.take(8)}, attempt $nextAttempt")
                sendPacket(packet)
                schedule(messageId)
            }
    }
}
