import com.correx.core.events.events.OrchestrationPausedEvent
import com.correx.core.events.events.OrchestrationResumedEvent
import com.correx.core.events.events.StageCompletedEvent
import com.correx.core.events.events.StageFailedEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.events.WorkflowCompletedEvent
import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.types.TransitionId
import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.SessionProjector
import com.correx.core.sessions.SessionStatus
import com.correx.testing.fixtures.EventFixtures.stored
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class SessionProjectorTest {

    private val projector = SessionProjector(DefaultSessionReducer())

    @Test
    fun `full lifecycle transitions correctly`() {
        var state = projector.initial()

        val events = listOf(
            stored(eventId = EventId("e1"), payload = WorkflowStartedEvent(SessionId("s1"), workflowId = "test-wf", startStageId = StageId("st-1"))),
            stored(eventId = EventId("e2"), payload = OrchestrationPausedEvent(SessionId("s1"), stageId = StageId("st-1"), reason = "APPROVAL_PENDING")),
            stored(eventId = EventId("e3"), payload = OrchestrationResumedEvent(SessionId("s1"), stageId = StageId("st-1"))),
            stored(eventId = EventId("e4"), payload = WorkflowCompletedEvent(SessionId("s1"), terminalStageId = StageId("st-1"), totalStages = 0))
        )

        events.forEach {
            state = projector.apply(state, it)
        }

        assertEquals(SessionStatus.COMPLETED, state.status)
    }

    @Test
    fun `failed stage transitions session to FAILED`() {
        var state = projector.initial()

        val events = listOf(
            stored(
                eventId = EventId("e1"),
                payload = WorkflowStartedEvent(SessionId("s1"), workflowId = "test-wf", startStageId = StageId("st-1"))
            ),
            stored(
                eventId = EventId("e2"),
                payload = TransitionExecutedEvent(
                    sessionId = SessionId("s1"),
                    from = StageId("draft"),
                    to = StageId("draft"),
                    transitionId = TransitionId("t1")
                )
            ),
            stored(
                eventId = EventId("e3"),
                payload = StageFailedEvent(
                    sessionId = SessionId("s1"),
                    stageId = StageId("draft"),
                    transitionId = TransitionId("t1"),
                    reason = "boom"
                )
            )
        )

        events.forEach {
            state = projector.apply(state, it)
        }

        assertEquals(SessionStatus.FAILED, state.status)
    }

    @Test
    fun `transition events keep session ACTIVE`() {
        var state = projector.initial()

        val events = listOf(
            stored(
                eventId = EventId("e1"),
                payload = WorkflowStartedEvent(SessionId("s1"), workflowId = "test-wf", startStageId = StageId("st-1"))
            ),
            stored(
                eventId = EventId("e2"),
                payload = TransitionExecutedEvent(
                    sessionId = SessionId("s1"),
                    from = StageId("a"),
                    to = StageId("b"),
                    transitionId = TransitionId("t1")
                )
            ),
            stored(
                eventId = EventId("e3"),
                payload = TransitionExecutedEvent(
                    sessionId = SessionId("s1"),
                    from = StageId("b"),
                    to = StageId("b"),
                    transitionId = TransitionId("t1")
                )
            ),
            stored(
                eventId = EventId("e4"),
                payload = StageCompletedEvent(
                    sessionId = SessionId("s1"),
                    stageId = StageId("b"),
                    transitionId = TransitionId("t1")
                )
            )
        )

        events.forEach {
            state = projector.apply(state, it)
        }

        assertEquals(SessionStatus.ACTIVE, state.status)
    }

    @Test
    fun `projection replay is deterministic`() {

        val events = listOf(
            stored(
                eventId = EventId("e1"),
                payload = WorkflowStartedEvent(SessionId("s1"), workflowId = "test-wf", startStageId = StageId("st-1"))
            ),
            stored(
                eventId = EventId("e2"),
                payload = WorkflowCompletedEvent(SessionId("s1"), terminalStageId = StageId("st-1"), totalStages = 0)
            )
        )

        val state1 = events.fold(projector.initial()) { state, event ->
            projector.apply(state, event)
        }

        val state2 = events.fold(projector.initial()) { state, event ->
            projector.apply(state, event)
        }

        assertEquals(state1, state2)
    }
}
