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.domain.DefaultApprovalEngine
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.events.types.GrantId
import com.correx.core.events.types.SessionId
import com.correx.core.sessions.ApprovalMode
import com.correx.testing.fixtures.request
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test

class GrantSecurityTest {

    private val engine = DefaultApprovalEngine()

    private val now = Instant.parse("2026-01-01T00:00:01Z")
    private val grantTime = Instant.parse("2026-01-01T00:00:00Z")

    private fun denyCtx() = ApprovalContext(
        ApprovalScopeIdentity(SessionId("s1"), null, null),
        ApprovalMode.DENY
    )

    // ─── 1. Operation isolation ───────────────────────────────────────────────

    @Test
    fun `SESSION grant for shell does not auto-approve write_file request`() {
        val req = request("r1", Tier.T2).copy(toolName = "write_file")
        val grant = ApprovalGrant(
            id = GrantId("g1"),
            scope = GrantScope.SESSION(toolName = "shell"),
            permittedTiers = setOf(Tier.T2),
            reason = "test",
            timestamp = grantTime
        )

        val decision = engine.evaluate(req, denyCtx(), listOf(grant), now)

        assertEquals(ApprovalStatus.PENDING, decision.state)
        assertNull(decision.outcome)
    }

    @Test
    fun `SESSION grant for write_file does not auto-approve shell request`() {
        val req = request("r1", Tier.T2).copy(toolName = "shell")
        val grant = ApprovalGrant(
            id = GrantId("g1"),
            scope = GrantScope.SESSION(toolName = "write_file"),
            permittedTiers = setOf(Tier.T2),
            reason = "test",
            timestamp = grantTime
        )

        val decision = engine.evaluate(req, denyCtx(), listOf(grant), now)

        assertEquals(ApprovalStatus.PENDING, decision.state)
        assertNull(decision.outcome)
    }

    // ─── 2. Tier ceiling ─────────────────────────────────────────────────────

    @Test
    fun `grant with max tier T2 does not auto-approve T3 request even with matching toolName`() {
        val req = request("r1", Tier.T3).copy(toolName = "shell")
        val grant = ApprovalGrant(
            id = GrantId("g1"),
            scope = GrantScope.SESSION(toolName = "shell"),
            permittedTiers = setOf(Tier.T2),
            reason = "test",
            timestamp = grantTime
        )

        val decision = engine.evaluate(req, denyCtx(), listOf(grant), now)

        assertEquals(ApprovalStatus.PENDING, decision.state)
        assertNull(decision.outcome)
    }

    @Test
    fun `grant with max tier T2 auto-approves T2 request with matching toolName (positive control)`() {
        val req = request("r1", Tier.T2).copy(toolName = "shell")
        val grant = ApprovalGrant(
            id = GrantId("g1"),
            scope = GrantScope.SESSION(toolName = "shell"),
            permittedTiers = setOf(Tier.T2),
            reason = "test",
            timestamp = grantTime
        )

        val decision = engine.evaluate(req, denyCtx(), listOf(grant), now)

        assertEquals(ApprovalOutcome.AUTO_APPROVED, decision.outcome)
        assertEquals(ApprovalStatus.COMPLETED, decision.state)
    }

    @Test
    fun `grant with max tier T2 auto-approves T1 request with matching toolName (ceiling covers lower tiers)`() {
        val req = request("r1", Tier.T1).copy(toolName = "shell")
        val grant = ApprovalGrant(
            id = GrantId("g1"),
            scope = GrantScope.SESSION(toolName = "shell"),
            permittedTiers = setOf(Tier.T2),
            reason = "test",
            timestamp = grantTime
        )

        val decision = engine.evaluate(req, denyCtx(), listOf(grant), now)

        assertEquals(ApprovalOutcome.AUTO_APPROVED, decision.outcome)
        assertEquals(ApprovalStatus.COMPLETED, decision.state)
    }

    @Test
    fun `grant with max tier T2 auto-approves T0 request with matching toolName (ceiling covers lowest tier)`() {
        val req = request("r1", Tier.T0).copy(toolName = "shell")
        val grant = ApprovalGrant(
            id = GrantId("g1"),
            scope = GrantScope.SESSION(toolName = "shell"),
            permittedTiers = setOf(Tier.T2),
            reason = "test",
            timestamp = grantTime
        )

        val decision = engine.evaluate(req, denyCtx(), listOf(grant), now)

        assertEquals(ApprovalOutcome.AUTO_APPROVED, decision.outcome)
        assertEquals(ApprovalStatus.COMPLETED, decision.state)
    }

    // ─── 3. No blanket grant ──────────────────────────────────────────────────

    @Test
    fun `SESSION grant with null toolName does not match any request (default-deny preserved)`() {
        val req = request("r1", Tier.T2).copy(toolName = "shell")
        val grant = ApprovalGrant(
            id = GrantId("g1"),
            scope = GrantScope.SESSION(toolName = null),
            permittedTiers = setOf(Tier.T2),
            reason = "test",
            timestamp = grantTime
        )

        val decision = engine.evaluate(req, denyCtx(), listOf(grant), now)

        assertEquals(ApprovalStatus.PENDING, decision.state)
        assertNull(decision.outcome)
    }

    @Test
    fun `SESSION grant with null toolName does not match request with null toolName`() {
        val req = request("r1", Tier.T2).copy(toolName = null)
        val grant = ApprovalGrant(
            id = GrantId("g1"),
            scope = GrantScope.SESSION(toolName = null),
            permittedTiers = setOf(Tier.T2),
            reason = "test",
            timestamp = grantTime
        )

        val decision = engine.evaluate(req, denyCtx(), listOf(grant), now)

        assertEquals(ApprovalStatus.PENDING, decision.state)
        assertNull(decision.outcome)
    }
}
