package com.correx.core.approvals.domain

import com.correx.core.approvals.ApprovalStatus
import com.correx.core.approvals.GrantScope
import com.correx.core.approvals.Tier
import com.correx.core.approvals.model.ApprovalContext
import com.correx.core.approvals.model.ApprovalGrant
import com.correx.core.approvals.model.ApprovalScopeIdentity
import com.correx.core.approvals.model.DomainApprovalRequest
import com.correx.core.events.types.ApprovalRequestId
import com.correx.core.events.types.GrantId
import com.correx.core.events.types.ProjectId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.ValidationReportId
import com.correx.core.sessions.ApprovalMode
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test

/**
 * Scope matching for the cross-session grant scopes (PROJECT, GLOBAL). A matching grant turns a
 * request that would otherwise PROMPT into an AUTO_APPROVED COMPLETED decision; a non-match leaves
 * it PENDING (PROMPT mode, tier above threshold).
 */
class GrantScopeMatchingTest {

    private val engine = DefaultApprovalEngine()
    private val now = Instant.parse("2026-06-21T00:00:00Z")
    private val projectA = ProjectId("/repo/a")
    private val projectB = ProjectId("/repo/b")

    private fun request(tool: String, tier: Tier = Tier.T3) = DomainApprovalRequest(
        id = ApprovalRequestId("req-1"),
        tier = tier,
        validationReportId = ValidationReportId("vr-1"),
        riskSummaryId = null,
        timestamp = now,
        toolName = tool,
    )

    private fun context(projectId: ProjectId?) = ApprovalContext(
        identity = ApprovalScopeIdentity(SessionId("s-1"), stageId = null, projectId = projectId),
        mode = ApprovalMode.PROMPT,
    )

    private fun grant(scope: GrantScope, tiers: Set<Tier> = setOf(Tier.T4)) = ApprovalGrant(
        id = GrantId("g-1"),
        scope = scope,
        permittedTiers = tiers,
        reason = "test",
        timestamp = now,
    )

    private fun isAutoApproved(d: com.correx.core.approvals.model.ApprovalDecision) =
        d.state == ApprovalStatus.COMPLETED && d.isApproved

    @Test
    fun `GLOBAL grant auto-approves the bound tool in any project`() {
        val g = grant(GrantScope.GLOBAL("write_file"))
        val decision = engine.evaluate(request("write_file"), context(projectA), listOf(g), now)
        assert(isAutoApproved(decision)) { "GLOBAL grant should auto-approve its tool anywhere" }
        assertEquals("grant:g-1", decision.reason)
    }

    @Test
    fun `GLOBAL grant does not approve a different tool`() {
        val g = grant(GrantScope.GLOBAL("write_file"))
        val decision = engine.evaluate(request("shell"), context(projectA), listOf(g), now)
        assertEquals(ApprovalStatus.PENDING, decision.state)
        assertNull(decision.outcome)
    }

    @Test
    fun `PROJECT grant approves only the matching project`() {
        val g = grant(GrantScope.PROJECT(projectA, "shell"))
        val match = engine.evaluate(request("shell"), context(projectA), listOf(g), now)
        assert(isAutoApproved(match)) { "PROJECT grant should approve in its own project" }

        val otherProject = engine.evaluate(request("shell"), context(projectB), listOf(g), now)
        assertEquals(ApprovalStatus.PENDING, otherProject.state)
    }

    @Test
    fun `PROJECT grant does not approve when the session has no project identity`() {
        val g = grant(GrantScope.PROJECT(projectA, "shell"))
        val decision = engine.evaluate(request("shell"), context(projectId = null), listOf(g), now)
        assertEquals(ApprovalStatus.PENDING, decision.state)
    }

    @Test
    fun `a wide grant with no tier ceiling auto-approves a T4 request`() {
        // The operator opted out of the prior T2 cap: a grant whose permittedTiers reach T4
        // authorizes up to T4 (ceiling semantics), so even a destructive call auto-clears.
        val g = grant(GrantScope.GLOBAL("delete"), tiers = setOf(Tier.T4))
        val decision = engine.evaluate(request("delete", tier = Tier.T4), context(projectA), listOf(g), now)
        assert(isAutoApproved(decision)) { "T4 request should auto-approve under a T4-permitting grant" }
    }
}
