package com.correx.core.approvals

import com.correx.core.approvals.model.ApprovalState
import com.correx.core.events.events.ApprovalDecisionResolvedEvent
import com.correx.core.events.events.ApprovalGrantCreatedEvent
import com.correx.core.events.events.ApprovalGrantExpiredEvent
import com.correx.core.events.events.ApprovalRequestedEvent
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.ToolInvokedEvent
import com.correx.core.events.types.ApprovalDecisionId
import com.correx.core.events.types.ApprovalRequestId
import com.correx.core.events.types.EventId
import com.correx.core.events.types.GrantId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.ValidationReportId
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class DefaultApprovalReducerTest {

    private val reducer = DefaultApprovalReducer()

    private val timestamp = Instant.parse("2026-01-01T00:00:00Z")
    private val sessionId = SessionId("session-1")

    private fun storedEvent(payload: com.correx.core.events.events.EventPayload, sequence: Long = 1L): StoredEvent {
        return StoredEvent(
            metadata = EventMetadata(
                eventId = EventId("event-$sequence"),
                sessionId = sessionId,
                timestamp = timestamp,
                schemaVersion = 1,
                causationId = null,
                correlationId = null
            ),
            sequence = sequence,
            sessionSequence = sequence,
            payload = payload
        )
    }

    @Test
    fun `ApprovalRequestedEvent adds request to state`() {
        val requestId = ApprovalRequestId("req-1")
        val payload = ApprovalRequestedEvent(
            requestId = requestId,
            tier = Tier.T2,
            validationReportId = ValidationReportId("report-1"),
            riskSummaryId = null,
            sessionId = sessionId,
            stageId = null,
            projectId = null
        )

        val state = reducer.reduce(ApprovalState(), storedEvent(payload))

        assertTrue(state.requests.containsKey(requestId))
        assertEquals(requestId, state.requests[requestId]?.id)
        assertEquals(Tier.T2, state.requests[requestId]?.tier)
    }

    @Test
    fun `ApprovalGrantCreatedEvent adds grant to state`() {
        val grantId = GrantId("grant-1")
        val payload = ApprovalGrantCreatedEvent(
            grantId = grantId,
            scope = GrantScope.SESSION(),
            permittedTiers = setOf(Tier.T1, Tier.T2),
            reason = "user approved",
            expiresAt = null,
            sessionId = sessionId,
            stageId = null,
            projectId = null
        )

        val state = reducer.reduce(ApprovalState(), storedEvent(payload))

        assertTrue(state.grants.containsKey(grantId))
        assertEquals(grantId, state.grants[grantId]?.id)
        assertEquals(GrantScope.SESSION(), state.grants[grantId]?.scope)
    }

    @Test
    fun `ApprovalGrantExpiredEvent removes grant from state`() {
        val grantId = GrantId("grant-2")

        val addPayload = ApprovalGrantCreatedEvent(
            grantId = grantId,
            scope = GrantScope.SESSION(),
            permittedTiers = setOf(Tier.T1),
            reason = "granted",
            expiresAt = null,
            sessionId = sessionId,
            stageId = null,
            projectId = null
        )
        val removePayload = ApprovalGrantExpiredEvent(grantId = grantId)

        val stateAfterAdd = reducer.reduce(ApprovalState(), storedEvent(addPayload, sequence = 1L))
        assertTrue(stateAfterAdd.grants.containsKey(grantId))

        val stateAfterRemove = reducer.reduce(stateAfterAdd, storedEvent(removePayload, sequence = 2L))
        assertFalse(stateAfterRemove.grants.containsKey(grantId))
    }

    @Test
    fun `ApprovalDecisionResolvedEvent adds decision to state`() {
        val decisionId = ApprovalDecisionId("decision-1")
        val requestId = ApprovalRequestId("req-1")
        val payload = ApprovalDecisionResolvedEvent(
            decisionId = decisionId,
            requestId = requestId,
            outcome = ApprovalOutcome.APPROVED,
            status = ApprovalStatus.COMPLETED,
            tier = Tier.T2,
            resolutionTimestamp = timestamp,
            reason = null
        )

        val state = reducer.reduce(ApprovalState(), storedEvent(payload))

        assertTrue(state.decisions.containsKey(decisionId))
        assertEquals(requestId, state.decisions[decisionId]?.requestId)
        assertEquals(ApprovalOutcome.APPROVED, state.decisions[decisionId]?.outcome)
    }

    @Test
    fun `unknown events pass state through unchanged`() {
        val initial = ApprovalState()
        val state = reducer.reduce(initial, storedEvent(ToolInvokedEvent("tool")))
        assertEquals(initial, state)
    }
}
