package com.correx.core.kernel.retry

import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.RetryAttemptedEvent
import com.correx.core.events.execution.RetryPolicy
import com.correx.core.events.orchestration.OrchestrationState
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import kotlinx.coroutines.delay
import kotlinx.datetime.Clock
import java.util.*

class DefaultRetryCoordinator(
    private val eventStore: EventStore,
) : RetryCoordinator {

    override suspend fun decide(
        sessionId: SessionId,
        stageId: StageId,
        gate: String,
        failureReason: String,
        state: OrchestrationState,
        policy: RetryPolicy,
    ): RetryDecision {
        val fingerprint = FailureFingerprint.of(failureReason)
        val prevFingerprint = state.gateFailureFingerprints[gate]
        val progressed = prevFingerprint != fingerprint
        val charged = !progressed
        val currentCount = state.gateRetryBudgets[gate] ?: 0
        val newCount = if (charged) currentCount + 1 else currentCount
        val maxAttempts = policy.maxAttemptsFor(gate)
        if (charged && newCount > maxAttempts) {
            return RetryDecision.Exhausted
        }

        eventStore.append(
            NewEvent(
                metadata = EventMetadata(
                    eventId = EventId(UUID.randomUUID().toString()),
                    sessionId = sessionId,
                    timestamp = Clock.System.now(),
                    schemaVersion = 1,
                    causationId = null,
                    correlationId = null,
                ),
                payload = RetryAttemptedEvent(
                    sessionId = sessionId,
                    stageId = stageId,
                    attemptNumber = newCount,
                    maxAttempts = maxAttempts,
                    failureReason = failureReason,
                    gate = gate,
                    fingerprint = fingerprint,
                    charged = charged,
                ),
            ),
        )
        if (policy.backoffMs > 0L) {
            delay(policy.backoffMs)
        }
        return RetryDecision.Retry
    }

    override suspend fun shouldRetry(
        sessionId: SessionId,
        stageId: StageId,
        currentAttempt: Int,
        policy: RetryPolicy,
        failureReason: String,
    ): Boolean {
        if (currentAttempt >= policy.maxAttempts) return false

        val eventId = EventId(UUID.randomUUID().toString())
        val event = NewEvent(
            metadata = EventMetadata(
                eventId = eventId,
                sessionId = sessionId,
                timestamp = Clock.System.now(),
                schemaVersion = 1,
                causationId = null,
                correlationId = null,
            ),
            payload = RetryAttemptedEvent(
                sessionId = sessionId,
                stageId = stageId,
                attemptNumber = currentAttempt + 1,
                maxAttempts = policy.maxAttempts,
                failureReason = failureReason,
            ),
        )

        eventStore.append(event)

        if (policy.backoffMs > 0L) {
            delay(policy.backoffMs)
        }

        return true
    }
}
