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.SteeringNoteAddedEvent
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.talkie.DefaultTalkieReducer
import com.correx.core.talkie.TalkieProjector
import com.correx.core.talkie.model.StageOutcomeKind
import com.correx.core.talkie.model.WorkflowStatus
import com.correx.testing.fixtures.EventFixtures.stored
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test

class TalkieProjectorTest {

    private val reducer = DefaultTalkieReducer()
    private val projector = TalkieProjector(reducer)
    private val sessionId = SessionId("s1")
    private val stageId = StageId("stage-1")
    private val workflowId = "workflow-test"

    @Test
    fun `initial state has IDLE status and empty collections`() {
        val state = projector.initial()
        assertEquals(WorkflowStatus.IDLE, state.workflowStatus)
        assertNull(state.currentStageId)
        assertEquals(0, state.l2Memory.size)
        assertEquals(0, state.conversationHistory.size)
    }

    @Test
    fun `should set RUNNING on WorkflowStartedEvent`() {
        val initialState = projector.initial()
        val started =
            projector.apply(initialState, stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)))
        assertEquals(WorkflowStatus.RUNNING, started.workflowStatus)
        assertEquals(stageId, started.currentStageId)
    }

    @Test
    fun `should set COMPLETED on WorkflowCompletedEvent`() {
        val initialState = projector.initial()
        val started =
            projector.apply(initialState, stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)))
        val completed =
            projector.apply(started, stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1)))
        assertEquals(WorkflowStatus.COMPLETED, completed.workflowStatus)
        assertNull(completed.currentStageId)
    }

    @Test
    fun `should set FAILED on WorkflowFailedEvent`() {
        val initialState = projector.initial()
        val started =
            projector.apply(initialState, stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)))
        val failed =
            projector.apply(
                started,
                stored(payload = WorkflowFailedEvent(sessionId, stageId, "Something went wrong", false)),
            )
        assertEquals(WorkflowStatus.FAILED, failed.workflowStatus)
        assertNull(failed.currentStageId)
    }

    @Test
    fun `should set PAUSED on OrchestrationPausedEvent`() {
        val initialState = projector.initial()
        val started =
            projector.apply(initialState, stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)))
        assertEquals(WorkflowStatus.RUNNING, started.workflowStatus)
        val paused =
            projector.apply(started, stored(payload = OrchestrationPausedEvent(sessionId, stageId, "Tool approval")))
        assertEquals(WorkflowStatus.PAUSED, paused.workflowStatus)
    }

    @Test
    fun `should set RUNNING after resume on pause`() {
        val initialState = projector.initial()
        val started =
            projector.apply(initialState, stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)))
        val paused =
            projector.apply(started, stored(payload = OrchestrationPausedEvent(sessionId, stageId, "Tool approval")))
        assertEquals(WorkflowStatus.PAUSED, paused.workflowStatus)
        val resumed =
            projector.apply(paused, stored(payload = OrchestrationResumedEvent(sessionId, stageId)))
        assertEquals(WorkflowStatus.RUNNING, resumed.workflowStatus)
    }

    @Test
    fun `should append l2Memory entry on StageCompletedEvent`() {
        val initialState = projector.initial()
        val completed =
            projector.apply(initialState, stored(payload = StageCompletedEvent(sessionId, stageId, StageId("t1"))))
        assertEquals(1, completed.l2Memory.size)
        assertEquals(stageId, completed.l2Memory[0].stageId)
        assertEquals(StageOutcomeKind.SUCCESS, completed.l2Memory[0].outcome)
    }

    @Test
    fun `should append l2Memory entry and clear currentStageId on StageFailedEvent`() {
        val initialState = projector.initial()
        val started =
            projector.apply(initialState, stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)))
        assertEquals(stageId, started.currentStageId)
        val failed =
            projector.apply(started, stored(payload = StageFailedEvent(sessionId, stageId, StageId("t1"), "timeout")))
        assertEquals(1, failed.l2Memory.size)
        assertEquals(stageId, failed.l2Memory[0].stageId)
        assertEquals(StageOutcomeKind.FAILURE, failed.l2Memory[0].outcome)
        assertNull(failed.currentStageId)
    }

    @Test
    fun `steering note does not modify conversation history in reducer`() {
        val initialState = projector.initial()
        val content = "This is a steering note"
        val updated =
            projector.apply(initialState, stored(payload = SteeringNoteAddedEvent(sessionId, content)))
        assertEquals(0, updated.conversationHistory.size)
        assertEquals(initialState.conversationHistory, updated.conversationHistory)
    }

    @Test
    fun `same events produce same result (determinism)`() {
        val initialState = projector.initial()
        val events = listOf(
            stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)),
            stored(payload = StageCompletedEvent(sessionId, StageId("stage-2"), StageId("t1"))),
            stored(payload = SteeringNoteAddedEvent(sessionId, "note")),
            stored(payload = StageFailedEvent(sessionId, stageId, StageId("t2"), "timeout")),
            stored(payload = WorkflowCompletedEvent(sessionId, stageId, 2)),
        )

        val result1 = events.fold(initialState) { state, event ->
            projector.apply(state, event)
        }
        val result2 = events.fold(initialState) { state, event ->
            projector.apply(state, event)
        }

        assertEquals(result1, result2)
    }

    @Test
    fun `full lifecycle stage complete then stage fail then resume then complete`() {
        val s0 = projector.initial()
        val s1 = projector.apply(s0, stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)))
        val s2 =
            projector.apply(s1, stored(payload = StageCompletedEvent(sessionId, StageId("stage-2"), StageId("t1"))))
        val s3 = projector.apply(s2, stored(payload = StageFailedEvent(sessionId, stageId, StageId("t2"), "timeout")))
        val s4 = projector.apply(s3, stored(payload = WorkflowStartedEvent(sessionId, workflowId, StageId("stage-3"))))
        val s5 = projector.apply(s4, stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1)))

        assertEquals(WorkflowStatus.RUNNING, s1.workflowStatus)
        assertEquals(StageId("stage-2"), s2.l2Memory[0].stageId)
        assertEquals(1, s2.l2Memory.size)
        assertEquals(2, s3.l2Memory.size)
        assertNull(s3.currentStageId)
        assertEquals(WorkflowStatus.RUNNING, s4.workflowStatus)
        assertEquals(StageId("stage-3"), s4.currentStageId)
        assertEquals(WorkflowStatus.COMPLETED, s5.workflowStatus)
        assertNull(s5.currentStageId)
    }

    @Test
    fun `unknown event type returns unchanged state`() {
        val initialState = projector.initial()
        val updated =
            projector.apply(initialState, stored(payload = com.correx.core.events.events.ToolInvokedEvent("test")))
        assertEquals(initialState, updated)
    }
}
