package com.correx.core.kernel.orchestration

import com.correx.core.approvals.ApprovalOutcome
import com.correx.core.approvals.ApprovalStatus
import com.correx.core.approvals.Tier
import com.correx.core.approvals.model.ApprovalDecision
import com.correx.core.approvals.model.DomainApprovalRequest
import com.correx.core.events.events.ApprovalDecisionResolvedEvent
import com.correx.core.events.events.ApprovalRequestedEvent
import com.correx.core.events.events.OrchestrationPausedEvent
import com.correx.core.events.events.OrchestrationResumedEvent
import com.correx.core.events.events.RiskAssessedEvent
import com.correx.core.events.risk.RiskSummary
import com.correx.core.events.types.ApprovalDecisionId
import com.correx.core.events.types.ApprovalRequestId
import com.correx.core.events.types.RiskSummaryId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.types.ValidationReportId
import com.correx.core.sessions.ApprovalMode
import com.correx.core.risk.RiskContext
import com.correx.core.risk.toApprovalTier
import com.correx.core.transitions.execution.StageExecutionResult
import com.correx.core.validation.pipeline.ValidationOutcome
import kotlinx.coroutines.CompletableDeferred
import kotlinx.datetime.Clock
import java.util.*
import java.util.concurrent.*
import java.util.concurrent.atomic.*

@Suppress("CyclomaticComplexMethod")
// Maps the operator profile's free-text approval_mode onto the engine's [ApprovalMode].
// Unset/unknown falls back to PROMPT so an absent profile keeps the human-in-the-loop
// default; only an explicit auto/yolo opts a session into unattended approval.
internal fun SessionOrchestrator.approvalModeFor(profileMode: String?): ApprovalMode =
    when (profileMode?.trim()?.lowercase()) {
        "deny" -> ApprovalMode.DENY
        "auto" -> ApprovalMode.AUTO
        "yolo" -> ApprovalMode.YOLO
        else -> ApprovalMode.PROMPT
    }

internal suspend fun SessionOrchestrator.requestStageApproval(
    sessionId: SessionId,
    stageId: StageId,
    preview: String,
): Boolean {
    val requestId = ApprovalRequestId(UUID.randomUUID().toString())
    val validationReportId = ValidationReportId(UUID.randomUUID().toString())
    val deferred = CompletableDeferred<ApprovalDecision>()
    pendingApprovals[requestId] = deferred

    emit(sessionId, OrchestrationPausedEvent(sessionId, stageId, "APPROVAL_PENDING"))
    emit(
        sessionId,
        ApprovalRequestedEvent(
            requestId = requestId,
            tier = Tier.T2,
            validationReportId = validationReportId,
            riskSummaryId = null,
            riskSummary = null,
            sessionId = sessionId,
            stageId = stageId,
            projectId = null,
            preview = preview,
        ),
    )

    return try {
        val decision = deferred.await()
        emitDecisionResolved(
            sessionId,
            DomainApprovalRequest(
                id = requestId,
                tier = Tier.T2,
                validationReportId = validationReportId,
                riskSummaryId = null,
                riskSummary = null,
                timestamp = Clock.System.now(),
            ),
            decision,
        )
        if (decision.isApproved) {
            emit(sessionId, OrchestrationResumedEvent(sessionId, stageId))
            true
        } else {
            emit(sessionId, OrchestrationResumedEvent(sessionId, stageId))
            false
        }
    } finally {
        pendingApprovals.remove(requestId)
    }
}

// --- private functions ---

internal suspend fun SessionOrchestrator.handleApproval(
    sessionId: SessionId,
    stageId: StageId,
    outcome: ValidationOutcome.NeedsApproval,
): StageExecutionResult {
    log.debug("[Orchestrator] approval required session={} stage={}", sessionId.value, stageId.value)
    val domainRequest = buildApprovalRequest(sessionId, stageId, outcome)
    val deferred = CompletableDeferred<ApprovalDecision>()
    pendingApprovals[domainRequest.id] = deferred

    emit(sessionId, OrchestrationPausedEvent(sessionId, stageId, "APPROVAL_PENDING"))
    emit(
        sessionId,
        ApprovalRequestedEvent(
            requestId = domainRequest.id,
            tier = domainRequest.tier,
            validationReportId = domainRequest.validationReportId,
            riskSummaryId = domainRequest.riskSummaryId,
            riskSummary = domainRequest.riskSummary,
            sessionId = sessionId,
            stageId = stageId,
            projectId = null,
        ),
    )

    return try {
        log.debug(
            "[Orchestrator] awaiting approval session={} requestId={}",
            sessionId.value, domainRequest.id.value,
        )
        val decision = deferred.await()
        log.debug(
            "[Orchestrator] approval resolved session={} approved={}",
            sessionId.value, decision.isApproved,
        )
        emitDecisionResolved(sessionId, domainRequest, decision)
        if (decision.isApproved) {
            emit(sessionId, OrchestrationResumedEvent(sessionId, stageId))
            StageExecutionResult.Success(emptyList())
        } else {
            emit(sessionId, OrchestrationResumedEvent(sessionId, stageId))
            StageExecutionResult.Failure(decision.reason ?: "approval rejected", retryable = false)
        }
    } finally {
        pendingApprovals.remove(domainRequest.id)
    }
}

internal suspend fun SessionOrchestrator.buildApprovalRequest(
    sessionId: SessionId,
    stageId: StageId,
    outcome: ValidationOutcome.NeedsApproval,
): DomainApprovalRequest {
    val state = orchestrationRepository.getState(sessionId)
    val inferenceState = inferenceRepository.getInferenceState(sessionId)
    val riskSummary = riskAssessor.assess(
        RiskContext(
            validationReport = outcome.request.validationReport,
            orchestrationState = state,
            inferenceState = inferenceState,
        ),
    )
    val riskSummaryId = RiskSummaryId(UUID.randomUUID().toString())
    // RiskAssessedEvent records the indexed level+action summary; ApprovalRequestedEvent carries the full
    // authoritative RiskSummary (signals + rationale). Both derive from the same assessment value so
    // they cannot diverge.
    emit(
        sessionId,
        RiskAssessedEvent(sessionId, stageId, riskSummaryId, riskSummary.level, riskSummary.recommendedAction),
    )
    return DomainApprovalRequest(
        id = ApprovalRequestId(UUID.randomUUID().toString()),
        tier = riskSummary.level.toApprovalTier(),
        validationReportId = ValidationReportId(UUID.randomUUID().toString()),
        riskSummaryId = riskSummaryId,
        riskSummary = riskSummary,
        timestamp = Clock.System.now(),
    )
}

internal suspend fun SessionOrchestrator.emitDecisionResolved(
    sessionId: SessionId,
    domainRequest: DomainApprovalRequest,
    decision: ApprovalDecision,
) {
    emit(
        sessionId,
        ApprovalDecisionResolvedEvent(
            decisionId = ApprovalDecisionId(UUID.randomUUID().toString()),
            requestId = domainRequest.id,
            outcome = decision.outcome ?: ApprovalOutcome.REJECTED,
            status = ApprovalStatus.COMPLETED,
            tier = domainRequest.tier,
            resolutionTimestamp = Clock.System.now(),
            reason = decision.reason,
            userSteering = decision.userSteering,
        ),
    )
}
