import com.correx.core.events.events.OrchestrationPausedEvent
import com.correx.core.events.events.OrchestrationResumedEvent
import com.correx.core.events.events.SessionWorkspaceBoundEvent
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.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowStartedEvent
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.events.events.AgentInstructionsBoundEvent
import com.correx.core.events.events.OperatorProfileBoundEvent
import com.correx.core.events.events.ProjectProfileBoundEvent
import com.correx.core.sessions.BoundAgentInstructions
import com.correx.core.sessions.BoundProfile
import com.correx.core.sessions.BoundProjectProfile
import com.correx.core.sessions.BoundWorkspace
import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.SessionState
import com.correx.core.sessions.SessionStatus
import com.correx.testing.fixtures.EventFixtures.stored
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class DefaultSessionReducerTest {

    private val reducer = DefaultSessionReducer()

    private val sessionId = SessionId("session-1")

    @Test
    fun `WorkflowStartedEvent leaves CREATED status`() {
        val state = initialState()

        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = StageId("st-1"))
            )
        )

        assertEquals(SessionStatus.CREATED, result.status)
    }

    @Test
    fun `OrchestrationPausedEvent leaves ACTIVE status`() {
        val state = activeState()

        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = OrchestrationPausedEvent(sessionId, stageId = StageId("st-1"), reason = "APPROVAL_PENDING")
            )
        )

        assertEquals(SessionStatus.ACTIVE, result.status)
    }

    @Test
    fun `OrchestrationResumedEvent leaves PAUSED status`() {
        val state = pausedState()

        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = OrchestrationResumedEvent(sessionId, stageId = StageId("st-1"))
            )
        )

        assertEquals(SessionStatus.PAUSED, result.status)
    }

    @Test
    fun `WorkflowCompletedEvent flips to COMPLETED status`() {
        val state = activeState()

        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = WorkflowCompletedEvent(sessionId, terminalStageId = StageId("st-1"), totalStages = 0)
            )
        )

        assertEquals(SessionStatus.COMPLETED, result.status)
    }

    @Test
    fun `WorkflowFailedEvent flips to FAILED status`() {
        val state = activeState()

        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = WorkflowFailedEvent(sessionId, stageId = StageId("st-1"), reason = "error", retryExhausted = false)
            )
        )

        assertEquals(SessionStatus.FAILED, result.status)
    }

    @Test
    fun `TransitionExecutedEvent from StageStartedEvent sets ACTIVE status`() {
        val state = pausedState()

        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = TransitionExecutedEvent(
                    sessionId,
                    from = StageId("st-1"),
                    to = StageId("st-1"),
                    transitionId = TransitionId("transition-a")
                )
            )
        )

        assertEquals(SessionStatus.ACTIVE, result.status)
    }

    @Test
    fun `StageCompletedEvent sets ACTIVE status`() {
        val state = pausedState()

        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = StageCompletedEvent(
                    sessionId,
                    stageId = StageId("stage-a"),
                    transitionId = TransitionId("transition-a")
                )
            )
        )

        assertEquals(SessionStatus.ACTIVE, result.status)
    }

    @Test
    fun `StageFailedEvent sets FAILED status`() {
        val state = activeState()

        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = StageFailedEvent(
                    sessionId,
                    stageId = StageId("stage-a"),
                    transitionId = TransitionId("transition-a"),
                    reason = "boom"
                )
            )
        )

        assertEquals(SessionStatus.FAILED, result.status)
    }

    @Test
    fun `TransitionExecutedEvent sets ACTIVE status`() {
        val state = pausedState()

        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = TransitionExecutedEvent(
                    sessionId,
                    from = StageId("stage-a"),
                    to = StageId("stage-b"),
                    transitionId = TransitionId("transition-a"),
                )
            )
        )

        assertEquals(SessionStatus.ACTIVE, result.status)
    }

    @Test
    fun `createdAt is initialized once`() {
        val timestamp = Instant.parse("2026-01-01T00:00:00Z")

        val result = reducer.reduce(
            state = initialState(),
            event = stored(
                payload = WorkflowStartedEvent(sessionId, workflowId = "test-wf", startStageId = StageId("st-1")),
                timestamp = timestamp
            )
        )

        assertEquals(timestamp, result.createdAt)
    }

    @Test
    fun `createdAt is preserved after initialization`() {
        val createdAt = Instant.parse("2026-01-01T00:00:00Z")
        val updatedAt = Instant.parse("2026-01-02T00:00:00Z")

        val state = activeState().copy(
            createdAt = createdAt
        )

        val result = reducer.reduce(
            state = state,
            event = stored(
                payload = OrchestrationPausedEvent(sessionId, stageId = StageId("st-1"), reason = "APPROVAL_PENDING"),
                timestamp = updatedAt
            )
        )

        assertEquals(createdAt, result.createdAt)
    }

    @Test
    fun `updatedAt always reflects latest event timestamp`() {
        val timestamp = Instant.parse("2026-01-02T00:00:00Z")

        val result = reducer.reduce(
            state = activeState(),
            event = stored(
                payload = OrchestrationPausedEvent(sessionId, stageId = StageId("st-1"), reason = "APPROVAL_PENDING"),
                timestamp = timestamp
            )
        )

        assertEquals(timestamp, result.updatedAt)
    }

    @Test
    fun `SessionWorkspaceBoundEvent sets boundWorkspace in state`() {
        val result = reducer.reduce(
            state = initialState(),
            event = stored(
                sessionId = sessionId,
                payload = SessionWorkspaceBoundEvent(
                    sessionId = sessionId,
                    workspaceRoot = "/home/user/project",
                    allowedPaths = listOf("/home/user/project"),
                )
            )
        )

        assertEquals(
            BoundWorkspace(
                workspaceRoot = "/home/user/project",
                allowedPaths = listOf("/home/user/project"),
            ),
            result.boundWorkspace,
        )
    }

    @Test
    fun `boundWorkspace is preserved by subsequent unrelated events`() {
        val withWorkspace = initialState().copy(
            boundWorkspace = BoundWorkspace(
                workspaceRoot = "/home/user/project",
                allowedPaths = listOf("/home/user/project"),
            )
        )

        val result = reducer.reduce(
            state = withWorkspace,
            event = stored(
                sessionId = sessionId,
                payload = WorkflowStartedEvent(sessionId, workflowId = "wf", startStageId = StageId("s1"))
            )
        )

        assertEquals(
            BoundWorkspace(
                workspaceRoot = "/home/user/project",
                allowedPaths = listOf("/home/user/project"),
            ),
            result.boundWorkspace,
        )
    }

    @Test
    fun `ProjectProfileBoundEvent reduces into boundProjectProfile`() {
        val result = reducer.reduce(
            state = initialState(),
            event = stored(
                sessionId = sessionId,
                payload = ProjectProfileBoundEvent(
                    sessionId = sessionId,
                    workspaceRoot = "/repo",
                    about = "kernel project",
                    conventions = listOf("no bare try-catch"),
                    commands = mapOf("test" to "./gradlew check"),
                ),
            ),
        )

        assertEquals(
            BoundProjectProfile(
                about = "kernel project",
                conventions = listOf("no bare try-catch"),
                commands = mapOf("test" to "./gradlew check"),
            ),
            result.boundProjectProfile,
        )
    }

    @Test
    fun `boundProjectProfile is preserved by subsequent unrelated events`() {
        val withProfile = initialState().copy(
            boundProjectProfile = BoundProjectProfile(
                about = "kernel project",
                conventions = listOf("no bare try-catch"),
                commands = emptyMap(),
            ),
        )

        val result = reducer.reduce(
            state = withProfile,
            event = stored(
                sessionId = sessionId,
                payload = WorkflowStartedEvent(sessionId, workflowId = "wf", startStageId = StageId("s1")),
            ),
        )

        assertEquals("kernel project", result.boundProjectProfile?.about)
    }

    @Test
    fun `AgentInstructionsBoundEvent reduces into boundAgentInstructions`() {
        val result = reducer.reduce(
            state = initialState(),
            event = stored(
                sessionId = sessionId,
                payload = AgentInstructionsBoundEvent(
                    sessionId = sessionId,
                    workspaceRoot = "/repo",
                    sources = listOf("CLAUDE.md", "AGENTS.md"),
                    content = "# CLAUDE.md\n\nbe careful",
                ),
            ),
        )

        assertEquals(
            BoundAgentInstructions(
                sources = listOf("CLAUDE.md", "AGENTS.md"),
                content = "# CLAUDE.md\n\nbe careful",
            ),
            result.boundAgentInstructions,
        )
    }

    @Test
    fun `boundAgentInstructions is preserved by subsequent unrelated events`() {
        val withInstructions = initialState().copy(
            boundAgentInstructions = BoundAgentInstructions(
                sources = listOf("CLAUDE.md"),
                content = "# CLAUDE.md\n\nrules",
            ),
        )

        val result = reducer.reduce(
            state = withInstructions,
            event = stored(
                sessionId = sessionId,
                payload = WorkflowStartedEvent(sessionId, workflowId = "wf", startStageId = StageId("s1")),
            ),
        )

        assertEquals(listOf("CLAUDE.md"), result.boundAgentInstructions?.sources)
    }

    private fun initialState() =
        SessionState(
            status = SessionStatus.CREATED
        )

    @Test
    fun `OperatorProfileBoundEvent populates boundProfile`() {
        val result = reducer.reduce(
            state = activeState(),
            event = stored(
                sessionId = sessionId,
                payload = OperatorProfileBoundEvent(
                    sessionId = sessionId,
                    about = "Expert Kotlin engineer",
                    approvalMode = "auto",
                    preferredModels = listOf("model-x"),
                    conventions = listOf("no var"),
                ),
            ),
        )

        val profile = result.boundProfile
        assertEquals("Expert Kotlin engineer", profile?.about)
        assertEquals("auto", profile?.approvalMode)
        assertEquals(listOf("model-x"), profile?.preferredModels)
        assertEquals(listOf("no var"), profile?.conventions)
    }

    @Test
    fun `unrelated event leaves boundProfile unchanged`() {
        val state = activeState().copy(
            boundProfile = BoundProfile(
                about = "original",
                approvalMode = "",
                preferredModels = emptyList(),
                conventions = emptyList(),
            ),
        )
        val result = reducer.reduce(
            state = state,
            event = stored(
                sessionId = sessionId,
                payload = SessionWorkspaceBoundEvent(
                    sessionId = sessionId,
                    workspaceRoot = "/ws",
                    allowedPaths = listOf("/ws"),
                ),
            ),
        )

        assertEquals("original", result.boundProfile?.about)
    }

    @Test
    fun `WorkflowCompletedEvent for freestyle planning stays ACTIVE (hands off to execution)`() {
        val result = reducer.reduce(
            state = activeState(),
            event = stored(
                sessionId = sessionId,
                payload = WorkflowCompletedEvent(
                    sessionId,
                    terminalStageId = StageId("architect"),
                    totalStages = 2,
                    workflowId = "freestyle_planning",
                ),
            ),
        )

        assertEquals(SessionStatus.ACTIVE, result.status)
    }

    private fun activeState() =
        SessionState(
            status = SessionStatus.ACTIVE
        )

    private fun pausedState() =
        SessionState(
            status = SessionStatus.PAUSED
        )
}
