package com.correx.apps.server.bridge

import com.correx.apps.server.protocol.AssessedIssueDto
import com.correx.apps.server.protocol.PauseReason
import com.correx.apps.server.protocol.RiskSummaryDto
import com.correx.apps.server.protocol.ServerMessage
import com.correx.apps.server.protocol.toDto
import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.events.events.ApprovalDecisionResolvedEvent
import com.correx.core.events.events.ApprovalRequestedEvent
import com.correx.core.events.events.ArtifactContentStoredEvent
import com.correx.core.events.events.ArtifactCreatedEvent
import com.correx.core.events.events.ArtifactValidatedEvent
import com.correx.core.events.events.ArtifactValidatingEvent
import com.correx.core.events.events.ExecutionPlanLockedEvent
import com.correx.core.events.events.ChatSessionStartedEvent
import com.correx.core.events.events.ChatTurnEvent
import com.correx.core.events.events.ClarificationRequestedEvent
import com.correx.core.events.events.InferenceCompletedEvent
import com.correx.core.events.events.SessionWorkspaceBoundEvent
import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.events.events.InferenceFailedEvent
import com.correx.core.events.events.InferenceStartedEvent
import com.correx.core.events.events.InferenceTimeoutEvent
import com.correx.core.events.events.RetryAttemptedEvent
import com.correx.core.events.events.ModelLoadedEvent
import com.correx.core.events.events.ModelUnloadedEvent
import com.correx.core.events.events.OrchestrationPausedEvent
import com.correx.core.events.events.OrchestrationResumedEvent
import com.correx.core.events.events.PreemptRedirectBlockedEvent
import com.correx.core.events.events.PreemptRedirectEvent
import com.correx.core.events.events.StageCompletedEvent
import com.correx.core.events.events.StageFailedEvent
import com.correx.core.events.events.StoredEvent
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.ToolCallAssessedEvent
import com.correx.core.events.events.ToolInvocationRequestedEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.events.WorkflowCompletedEvent
import com.correx.core.events.events.RouterNarrationEvent
import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowProposedEvent
import com.correx.core.events.risk.RiskAction
import com.correx.core.events.types.ArtifactId
import org.slf4j.LoggerFactory

private val log = LoggerFactory.getLogger("DomainEventMapper")

class DomainEventMapper(private val artifactStore: ArtifactStore = NoopArtifactStore) {
    suspend fun map(event: StoredEvent, sessionSequence: Long = 0L): ServerMessage? =
        domainEventToServerMessage(event, artifactStore, sessionSequence = sessionSequence)
}

private object NoopArtifactStore : ArtifactStore {
    override suspend fun put(bytes: ByteArray): ArtifactId = ArtifactId("noop")
    override suspend fun get(id: ArtifactId): ByteArray? = null
    override suspend fun flushBefore(commit: suspend () -> Unit) = commit()
}

@Suppress("CyclomaticComplexMethod")
suspend fun domainEventToServerMessage(
    event: StoredEvent,
    artifactStore: ArtifactStore,
    sessionSequence: Long = 0L,
): ServerMessage? {
    val seq = event.sequence
    return when (val p = event.payload) {
        is ChatSessionStartedEvent -> ServerMessage.SessionAnnounced(
            sessionId = p.sessionId,
            workflowId = "chat",
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is WorkflowStartedEvent -> ServerMessage.SessionAnnounced(
            sessionId = p.sessionId,
            workflowId = p.workflowId,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is SessionWorkspaceBoundEvent -> ServerMessage.SessionWorkspaceBound(
            sessionId = p.sessionId,
            workspaceRoot = p.workspaceRoot,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ChatTurnEvent -> ServerMessage.ChatTurn(
            sessionId = p.sessionId,
            turnId = p.turnId,
            role = p.role.name,
            content = p.content,
            latencyMs = p.latencyMs,
            totalTokens = p.tokensUsed?.totalTokens,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is WorkflowCompletedEvent -> ServerMessage.SessionCompleted(
            sessionId = p.sessionId,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is WorkflowFailedEvent -> ServerMessage.SessionFailed(
            sessionId = p.sessionId,
            reason = p.reason,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is TransitionExecutedEvent -> ServerMessage.StageStarted(
            sessionId = p.sessionId,
            stageId = p.to,
            occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is StageCompletedEvent -> ServerMessage.StageCompleted(
            sessionId = p.sessionId,
            stageId = p.stageId,
            occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is StageFailedEvent -> ServerMessage.StageFailed(
            sessionId = p.sessionId,
            stageId = p.stageId,
            reason = p.reason,
            occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is OrchestrationPausedEvent -> mapOrchestrationPaused(p, seq, sessionSequence)
        is OrchestrationResumedEvent -> ServerMessage.SessionResumed(
            sessionId = p.sessionId,
            stageId = p.stageId,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        is InferenceStartedEvent -> ServerMessage.InferenceStarted(
            sessionId = p.sessionId,
            stageId = p.stageId,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is InferenceCompletedEvent -> mapInferenceCompleted(event, p, artifactStore, sessionSequence)
        is InferenceTimeoutEvent -> ServerMessage.InferenceTimedOut(
            sessionId = p.sessionId,
            stageId = p.stageId,
            elapsedMs = p.timeoutMs,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        is InferenceFailedEvent -> ServerMessage.InferenceFailed(
            sessionId = p.sessionId,
            stageId = p.stageId,
            reason = p.reason,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        is RetryAttemptedEvent -> ServerMessage.RetryAttempted(
            sessionId = p.sessionId,
            stageId = p.stageId,
            attemptNumber = p.attemptNumber,
            maxAttempts = p.maxAttempts,
            failureReason = p.failureReason,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ToolInvocationRequestedEvent -> ServerMessage.ToolStarted(
            sessionId = p.sessionId,
            toolName = p.toolName,
            tier = p.tier,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ToolExecutionCompletedEvent -> ServerMessage.ToolCompleted(
            sessionId = p.sessionId,
            toolName = p.toolName,
            outputSummary = p.receipt.outputSummary,
            occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
            diff = p.receipt.diff,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ToolExecutionFailedEvent -> ServerMessage.ToolFailed(
            sessionId = p.sessionId,
            toolName = p.toolName,
            reason = p.reason,
            occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ToolExecutionRejectedEvent -> ServerMessage.ToolRejected(
            sessionId = p.sessionId,
            toolName = p.toolName,
            reason = p.reason,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ToolCallAssessedEvent -> ServerMessage.ToolAssessed(
            sessionId = p.sessionId,
            stageId = p.stageId,
            toolName = p.toolName,
            disposition = p.disposition.name,
            issues = p.issues.map { AssessedIssueDto(it.code, it.message, it.severity) },
            occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ApprovalRequestedEvent -> mapApprovalRequested(p, seq, sessionSequence)
        is ClarificationRequestedEvent -> ServerMessage.ClarificationRequired(
            sessionId = p.sessionId,
            requestId = p.requestId,
            stageId = p.stageId,
            questions = p.questions,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        is WorkflowProposedEvent -> ServerMessage.WorkflowProposed(
            sessionId = p.sessionId,
            proposalId = p.proposalId,
            prompt = p.prompt,
            candidates = p.candidates,
            originalRequest = p.originalRequest,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        is ApprovalDecisionResolvedEvent -> ServerMessage.ApprovalResolved(
            // ApprovalDecisionResolvedEvent has no sessionId on its payload — read it from the event envelope
            sessionId = event.metadata.sessionId,
            requestId = p.requestId,
            outcome = p.outcome.name,
            reason = p.reason,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        is RouterNarrationEvent -> ServerMessage.Narration(
            sessionId = p.sessionId,
            content = p.content,
            stageId = p.stageId,
            latencyMs = p.latencyMs,
            totalTokens = p.tokensUsed?.totalTokens,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        is ArtifactCreatedEvent -> ServerMessage.ArtifactCreated(
            sessionId = p.sessionId,
            stageId = p.stageId,
            artifactId = p.artifactId,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        is ArtifactValidatedEvent -> ServerMessage.ArtifactValidated(
            sessionId = p.sessionId,
            stageId = p.stageId,
            artifactId = p.artifactId,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        // Transient pre-validation marker, emitted microseconds before Validated or a
        // stage failure — either of those carries the outcome the operator cares about.
        is ArtifactValidatingEvent -> null
        is ExecutionPlanLockedEvent -> ServerMessage.PlanLocked(
            sessionId = p.sessionId,
            workflowId = p.workflowId,
            stageIds = p.stageIds,
            sequence = seq,
            sessionSequence = sessionSequence,
        )
        is ModelLoadedEvent -> ServerMessage.ModelChanged(
            modelId = p.modelId,
            providerId = p.providerId.value,
            loaded = true,
        )
        is ModelUnloadedEvent -> ServerMessage.ModelChanged(
            modelId = p.modelId,
            providerId = p.providerId.value,
            loaded = false,
        )
        // Internal slot→CAS-hash bookkeeping (F-007 durable bridge); no operator-facing surface.
        is ArtifactContentStoredEvent -> null

        // Freestyle graph-rerouting bookkeeping. The deterministic record is in place; a dedicated
        // operator surface ships with the LLM-proposal + approval-confirm front-half.
        is PreemptRedirectEvent -> null
        is PreemptRedirectBlockedEvent -> null

        else -> {
            log.debug(
                "DomainEventMapper: unmapped payload type={} sessionId={} sequence={}",
                p::class.simpleName,
                event.metadata.sessionId,
                event.sequence,
            )
            null
        }
    }
}

private fun mapOrchestrationPaused(
    p: OrchestrationPausedEvent,
    seq: Long,
    sessionSequence: Long,
): ServerMessage {
    val reason = if (p.reason == "APPROVAL_PENDING") PauseReason.APPROVAL_PENDING else PauseReason.USER_REQUESTED
    return ServerMessage.SessionPaused(
        sessionId = p.sessionId,
        reason = reason,
        sequence = seq,
        sessionSequence = sessionSequence,
    )
}

private suspend fun mapInferenceCompleted(
    event: StoredEvent,
    p: InferenceCompletedEvent,
    artifactStore: ArtifactStore,
    sessionSequence: Long,
): ServerMessage = runCatching {
    artifactStore.get(p.responseArtifactId)?.toString(Charsets.UTF_8) ?: ""
}.getOrElse { "" }.run {
    ServerMessage.InferenceCompleted(
        sessionId = p.sessionId,
        stageId = p.stageId,
        outputSummary = this,
        responseText = this,
        occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
        sequence = event.sequence,
        sessionSequence = sessionSequence,
    )
}

private fun mapApprovalRequested(
    p: ApprovalRequestedEvent,
    seq: Long,
    sessionSequence: Long,
): ServerMessage =
    ServerMessage.ApprovalRequired(
        sessionId = p.sessionId,
        requestId = p.requestId,
        tier = p.tier,
        riskSummary = p.riskSummary?.toDto() ?: RiskSummaryDto(
            level = p.tier.name,
            factors = emptyList(),
            recommendedAction = RiskAction.PROMPT_USER.name,
            rationale = emptyList(),
        ),
        toolName = p.toolName,
        preview = p.preview,
        sequence = seq,
        sessionSequence = sessionSequence,
    )
