package com.correx.apps.server.replay

import com.correx.core.events.events.ApprovalDecisionResolvedEvent
import com.correx.core.events.events.ApprovalRequestedEvent
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.OrchestrationPausedEvent
import com.correx.core.events.events.OrchestrationResumedEvent
import com.correx.core.events.events.RetryAttemptedEvent
import com.correx.core.events.events.StageCompletedEvent
import com.correx.core.events.events.StageFailedEvent
import com.correx.core.events.events.ToolExecutionCompletedEvent
import com.correx.core.events.events.ToolExecutionFailedEvent
import com.correx.core.events.events.ToolExecutionRejectedEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.events.WorkflowCompletedEvent
import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.apps.server.serialization.payloadDiscriminator
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.serialization.eventJson
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.SessionId
import kotlinx.serialization.Serializable
import java.security.MessageDigest

@Serializable
data class TimelineEntry(
    val sequence: Long,
    val type: String,
    val stageId: String?,
    val summary: String,
)

@Serializable
data class ReplayReport(
    val sessionId: String,
    val eventCount: Long,
    val timeline: List<TimelineEntry>,
    val digest: String,
    val deterministic: Boolean,
)

class ReplayInspectionService(private val eventStore: EventStore) {

    fun inspect(sessionId: SessionId): ReplayReport {
        val events = eventStore.read(sessionId)
        val timeline = events.map { stored ->
            val element = eventJson.encodeToJsonElement(EventPayload.serializer(), stored.payload)
            toTimelineEntry(stored.sequence, stored.payload, payloadDiscriminator(element))
        }
        val digest = computeDigest(events)
        // Second digest from an independent store read — catches store-order or
        // serialization nondeterminism; must NOT reuse the already-loaded list.
        val digest2 = computeDigest(eventStore.read(sessionId))
        return ReplayReport(
            sessionId = sessionId.value,
            eventCount = events.size.toLong(),
            timeline = timeline,
            digest = digest,
            deterministic = digest == digest2,
        )
    }

    private fun toTimelineEntry(sequence: Long, payload: EventPayload, type: String): TimelineEntry =
        when (payload) {
            is WorkflowStartedEvent ->
                TimelineEntry(sequence, type, payload.startStageId.value, "Workflow started: ${payload.workflowId}")
            is StageCompletedEvent ->
                TimelineEntry(sequence, type, payload.stageId.value, "Stage completed: ${payload.stageId.value}")
            is StageFailedEvent -> TimelineEntry(
                sequence, type, payload.stageId.value, "Stage failed: ${payload.stageId.value} — ${payload.reason}",
            )
            is ToolExecutionCompletedEvent -> TimelineEntry(sequence, type, null, "Tool executed: ${payload.toolName}")
            is ToolExecutionFailedEvent ->
                TimelineEntry(sequence, type, null, "Tool failed: ${payload.toolName} — ${payload.reason}")
            is ToolExecutionRejectedEvent ->
                TimelineEntry(sequence, type, null, "Tool rejected: ${payload.toolName} — ${payload.reason}")
            is ApprovalRequestedEvent ->
                TimelineEntry(sequence, type, payload.stageId?.value, "Approval requested: tier ${payload.tier}")
            is ApprovalDecisionResolvedEvent ->
                TimelineEntry(sequence, type, null, "Approval resolved: ${payload.outcome}")
            is OrchestrationPausedEvent -> TimelineEntry(
                sequence,
                type,
                payload.stageId.value,
                "Orchestration paused at ${payload.stageId.value}: ${payload.reason}",
            )
            is OrchestrationResumedEvent -> TimelineEntry(
                sequence, type, payload.stageId.value, "Orchestration resumed at ${payload.stageId.value}",
            )
            is RetryAttemptedEvent -> TimelineEntry(
                sequence,
                type,
                payload.stageId.value,
                "Retry attempt ${payload.attemptNumber}/${payload.maxAttempts} at " +
                    "${payload.stageId.value}: ${payload.failureReason}",
            )
            is TransitionExecutedEvent -> TimelineEntry(
                sequence, type, payload.from.value, "Transition ${payload.from.value} → ${payload.to.value}",
            )
            is WorkflowCompletedEvent -> TimelineEntry(
                sequence,
                type,
                payload.terminalStageId.value,
                "Workflow completed after ${payload.totalStages} stages",
            )
            is WorkflowFailedEvent ->
                TimelineEntry(sequence, type, payload.stageId.value, "Workflow failed: ${payload.reason}")
            else -> TimelineEntry(sequence, type, null, type)
        }

    private fun computeDigest(events: List<StoredEvent>): String {
        val digest = MessageDigest.getInstance("SHA-256")
        for (event in events) {
            val element = eventJson.encodeToJsonElement(EventPayload.serializer(), event.payload)
            val type = payloadDiscriminator(element)
            val payloadJsonString = eventJson.encodeToString(EventPayload.serializer(), event.payload)
            val line = "${event.sequence}|$type|$payloadJsonString"
            digest.update(line.toByteArray(Charsets.UTF_8))
        }
        return digest.digest().joinToString("") { "%02x".format(it) }
    }
}
