package com.correx.core.approvals.domain

import com.correx.core.approvals.ApprovalOutcome
import com.correx.core.approvals.ApprovalStatus
import com.correx.core.approvals.GrantScope
import com.correx.core.approvals.Tier
import com.correx.core.approvals.isAtMost
import com.correx.core.approvals.model.ApprovalContext
import com.correx.core.approvals.model.ApprovalDecision
import com.correx.core.approvals.model.ApprovalGrant
import com.correx.core.approvals.model.DomainApprovalRequest
import com.correx.core.events.types.ApprovalDecisionId
import com.correx.core.sessions.ApprovalMode
import kotlinx.datetime.Instant

@Suppress("ReturnCount")
class DefaultApprovalEngine : ApprovalEngine {

    override fun evaluate(
        request: DomainApprovalRequest,
        context: ApprovalContext,
        grants: List<ApprovalGrant>,
        now: Instant
    ): ApprovalDecision {
        val decisionId = ApprovalDecisionId("decision:${request.id.value}")

        val matchingGrant = grants.firstOrNull { grant ->
            !isExpired(grant, now)
                && scopeMatches(grant.scope, context, request.toolName)
                // Ceiling semantics: grant authorizes up to its highest tier, not an exact set.
                && grant.permittedTiers.isNotEmpty()
                && request.tier.level <= grant.permittedTiers.maxOf { it.level }
        }

        if (matchingGrant != null) {
            return approved(decisionId, request, context, now, reason = "grant:${matchingGrant.id.value}")
        }

        if (context.mode == ApprovalMode.YOLO) {
            return approved(decisionId, request, context, now, reason = null)
        }

        val threshold = when (context.mode) {
            ApprovalMode.DENY   -> Tier.T0
            ApprovalMode.PROMPT -> Tier.T1
            ApprovalMode.AUTO   -> Tier.T2
            ApprovalMode.YOLO   -> error("unreachable")
        }

        return if (request.tier.isAtMost(threshold)) {
            approved(decisionId, request, context, now, reason = null)
        } else {
            ApprovalDecision(
                id = decisionId,
                requestId = request.id,
                outcome = null,
                state = ApprovalStatus.PENDING,
                tier = request.tier,
                contextSnapshot = context,
                resolutionTimestamp = null,
                reason = null,
                userSteering = request.userSteering
            )
        }
    }

    private fun approved(
        id: ApprovalDecisionId,
        request: DomainApprovalRequest,
        context: ApprovalContext,
        now: Instant,
        reason: String?
    ) = ApprovalDecision(
        id = id,
        requestId = request.id,
        outcome = ApprovalOutcome.AUTO_APPROVED,
        state = ApprovalStatus.COMPLETED,
        tier = request.tier,
        contextSnapshot = context,
        resolutionTimestamp = now,
        reason = reason,
        userSteering = request.userSteering
    )

    private fun isExpired(grant: ApprovalGrant, now: Instant): Boolean =
        grant.expiresAt != null && grant.expiresAt <= now

    private fun scopeMatches(scope: GrantScope, context: ApprovalContext, requestToolName: String?): Boolean =
        when (scope) {
            // SESSION/PROJECT/GLOBAL grants are bound to a specific tool name. A null toolName on
            // either side means the binding is absent; treat as no-match so a legacy/malformed grant
            // can never become a blanket approval. PROJECT additionally requires the active session's
            // workspace to resolve to the same projectId; GLOBAL applies to every session.
            is GrantScope.SESSION -> toolBound(scope.toolName, requestToolName)
            is GrantScope.STAGE   -> context.identity.stageId == scope.stageId
            is GrantScope.PROJECT -> toolBound(scope.toolName, requestToolName)
                && context.identity.projectId != null
                && context.identity.projectId == scope.projectId
            is GrantScope.GLOBAL  -> toolBound(scope.toolName, requestToolName)
        }

    private fun toolBound(grantToolName: String?, requestToolName: String?): Boolean =
        grantToolName != null && requestToolName != null && grantToolName == requestToolName
}
