package com.correx.apps.server.narration

import com.correx.core.events.events.ApprovalRequestedEvent
import com.correx.core.events.events.ExecutionPlanRejectedEvent
import com.correx.core.events.events.OrchestrationPausedEvent
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.WorkflowCompletedEvent
import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.SessionId
import com.correx.core.router.RouterFacade
import com.correx.core.router.model.NarrationTrigger
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory
import java.util.concurrent.ConcurrentHashMap

class NarrationSubscriber(
    private val eventStore: EventStore,
    private val routerFacade: RouterFacade,
    private val scope: CoroutineScope,
    private val maxPerRun: Int = DEFAULT_MAX_PER_RUN,
) {
    private data class SessionLane(val channel: Channel<NarrationTrigger>, var used: Int)

    private val lanes = ConcurrentHashMap<String, SessionLane>()

    fun start() {
        eventStore.subscribeAll().onEach { handle(it) }.launchIn(scope)
    }

    private fun handle(event: StoredEvent) {
        val sid = event.metadata.sessionId
        when (val p = event.payload) {
            is WorkflowStartedEvent -> lanes[sid.value]?.let { it.used = 0 }
            // Approval pauses narrate off this event, not OrchestrationPaused: it carries the
            // tool/tier/preview facts directly, where the paused event forced a racy lookup
            // against state the next event had not yet written.
            is ApprovalRequestedEvent -> enqueue(
                sid,
                NarrationTrigger(
                    kind = "paused",
                    instruction = approvalInstruction(p),
                    stageId = p.stageId?.value,
                ),
            )
            is StageCompletedEvent -> enqueue(
                sid,
                NarrationTrigger(
                    kind = "stage_completed",
                    instruction = "Stage ${p.stageId.value} completed. Summarise what was accomplished.",
                    stageId = p.stageId.value,
                ),
            )
            is StageFailedEvent -> enqueue(
                sid,
                NarrationTrigger(
                    kind = "stage_failed",
                    instruction = "Stage ${p.stageId.value} failed: ${p.reason}. Explain what went wrong.",
                    stageId = p.stageId.value,
                ),
            )
            is WorkflowCompletedEvent -> enqueue(
                sid,
                NarrationTrigger(
                    kind = "workflow_completed",
                    instruction = "The workflow finished successfully. Provide a brief summary.",
                    stageId = p.terminalStageId.value,
                ),
            )
            is WorkflowFailedEvent -> enqueue(
                sid,
                NarrationTrigger(
                    kind = "workflow_failed",
                    instruction = "The workflow failed in stage ${p.stageId.value}: ${p.reason}. Explain the failure to the user.",
                    stageId = p.stageId.value,
                ),
            )
            is ExecutionPlanRejectedEvent -> enqueue(
                sid,
                NarrationTrigger(
                    kind = "plan_rejected",
                    instruction = "The execution plan was rejected (${p.source}): ${p.reason}. Explain to the user what happened and what they can do next.",
                ),
            )
            is OrchestrationPausedEvent -> {
                // Approval pauses are narrated from ApprovalRequestedEvent above.
                if (!p.reason.contains("APPROVAL", ignoreCase = true)) {
                    enqueue(
                        sid,
                        NarrationTrigger(
                            kind = "paused",
                            instruction = "Orchestration paused in stage ${p.stageId.value}: ${p.reason}. Inform the user.",
                            stageId = p.stageId.value,
                        ),
                    )
                }
            }
            else -> Unit
        }
    }

    private fun approvalInstruction(event: ApprovalRequestedEvent): String {
        val stage = event.stageId?.value ?: "the current stage"
        val subject = event.toolName?.let { "of the '$it' tool (tier ${event.tier})" }
            ?: "(tier ${event.tier})"
        return buildString {
            append("The workflow paused in stage $stage awaiting your approval $subject. ")
            append("Tell the operator what is proposed and why it needs sign-off.")
            val preview = event.preview.orEmpty()
            if (preview.isNotBlank()) {
                append("\n\nProposed change:\n")
                append(preview.take(MAX_PREVIEW_CHARS))
            }
        }
    }

    private fun enqueue(sessionId: SessionId, trigger: NarrationTrigger) {
        val lane = lanes.computeIfAbsent(sessionId.value) { startLane(sessionId) }
        if (lane.used >= maxPerRun) {
            log.debug("narration budget reached for session {}; skipping {}", sessionId.value, trigger.kind)
            return
        }
        lane.used += 1
        lane.channel.trySend(trigger)
    }

    private fun startLane(sessionId: SessionId): SessionLane {
        val channel = Channel<NarrationTrigger>(capacity = Channel.UNLIMITED)
        scope.launch {
            for (trigger in channel) {
                runCatching { routerFacade.narrate(sessionId, trigger) }
                    .onFailure { e ->
                        if (e is CancellationException) throw e
                        log.warn(
                            "narration failed for session {} trigger {}: {}",
                            sessionId.value,
                            trigger.kind,
                            e.message,
                        )
                    }
            }
        }
        return SessionLane(channel, used = 0)
    }

    companion object {
        private const val DEFAULT_MAX_PER_RUN = 100
        private const val MAX_PREVIEW_CHARS = 800
        private val log = LoggerFactory.getLogger(NarrationSubscriber::class.java)
    }
}
