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.ToolInvokedEvent
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.TalkieState
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.Assertions.assertTrue
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() returns TalkieState with IDLE status and empty collections`() {
        val state = projector.initial()
        assertEquals(WorkflowStatus.IDLE, state.workflowStatus)
        assertNull(state.sessionId)
        assertNull(state.currentStageId)
        assertTrue(state.l2Memory.isEmpty())
        assertTrue(state.conversationHistory.isEmpty())
    }

    @Test
    fun `initial returns same state as reducer initial`() {
        val projectorState = projector.initial()
        val reducerState = reducer.initial
        assertEquals(reducerState, projectorState)
    }

    @Test
    fun `apply(WorkflowStartedEvent) delegates to reducer and sets RUNNING`() {
        val state = projector.initial()
        val updated = projector.apply(
            state,
            stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)),
        )
        assertEquals(sessionId, updated.sessionId)
        assertEquals(WorkflowStatus.RUNNING, updated.workflowStatus)
        assertEquals(stageId, updated.currentStageId)
    }

    @Test
    fun `apply(WorkflowCompletedEvent) delegates to reducer and sets COMPLETED`() {
        val started = projector.apply(
            projector.initial(),
            stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)),
        )
        val updated = projector.apply(
            started,
            stored(payload = WorkflowCompletedEvent(sessionId, stageId, 3)),
        )
        assertEquals(WorkflowStatus.COMPLETED, updated.workflowStatus)
        assertNull(updated.currentStageId)
        assertEquals(sessionId, updated.sessionId)
    }

    @Test
    fun `apply(WorkflowFailedEvent) delegates to reducer and sets FAILED`() {
        val started = projector.apply(
            projector.initial(),
            stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)),
        )
        val updated = projector.apply(
            started,
            stored(payload = WorkflowFailedEvent(sessionId, stageId, "timeout", false)),
        )
        assertEquals(WorkflowStatus.FAILED, updated.workflowStatus)
        assertNull(updated.currentStageId)
    }

    @Test
    fun `apply(OrchestrationPausedEvent) delegates to reducer and sets PAUSED`() {
        val started = projector.apply(
            projector.initial(),
            stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)),
        )
        val updated = projector.apply(
            started,
            stored(payload = OrchestrationPausedEvent(sessionId, stageId, "APPROVAL_PENDING")),
        )
        assertEquals(WorkflowStatus.PAUSED, updated.workflowStatus)
        assertEquals(sessionId, updated.sessionId)
    }

    @Test
    fun `apply(OrchestrationResumedEvent) delegates to reducer and sets RUNNING`() {
        val started = projector.apply(
            projector.initial(),
            stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)),
        )
        val paused = projector.apply(
            started,
            stored(payload = OrchestrationPausedEvent(sessionId, stageId, "APPROVAL_PENDING")),
        )
        val updated = projector.apply(
            paused,
            stored(payload = OrchestrationResumedEvent(sessionId, stageId)),
        )
        assertEquals(WorkflowStatus.RUNNING, updated.workflowStatus)
    }

    @Test
    fun `apply(StageCompletedEvent) delegates to reducer and appends l2Memory entry`() {
        val state = projector.initial()
        val updated = projector.apply(
            state,
            stored(payload = StageCompletedEvent(sessionId, stageId, StageId("t1"))),
        )
        assertEquals(1, updated.l2Memory.size)
        val entry = updated.l2Memory[0]
        assertEquals(stageId, entry.stageId)
        assertEquals(StageOutcomeKind.SUCCESS, entry.outcome)
    }

    @Test
    fun `apply(StageFailedEvent) delegates to reducer and appends FAILURE entry`() {
        val state = projector.apply(
            projector.initial(),
            stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)),
        )
        val updated = projector.apply(
            state,
            stored(payload = StageFailedEvent(sessionId, stageId, StageId("t1"), "timeout")),
        )
        assertEquals(1, updated.l2Memory.size)
        val entry = updated.l2Memory[0]
        assertEquals(stageId, entry.stageId)
        assertEquals(StageOutcomeKind.FAILURE, entry.outcome)
    }

    @Test
    fun `apply(SteeringNoteAddedEvent) accumulates the note into pendingSteeringNotes`() {
        val state = projector.initial()
        val updated = projector.apply(
            state,
            stored(payload = SteeringNoteAddedEvent(sessionId, "do something different")),
        )
        assertEquals(listOf("do something different"), updated.pendingSteeringNotes)
    }

    @Test
    fun `apply(ToolInvokedEvent) leaves state unchanged`() {
        val state = projector.initial()
        val updated = projector.apply(
            state,
            stored(payload = ToolInvokedEvent("test")),
        )
        assertEquals(state, updated)
    }

    @Test
    fun `apply produces same result as reducer reduce for every handled event`() {
        val state = projector.initial()
        val events = listOf(
            stored(payload = WorkflowStartedEvent(sessionId, workflowId, StageId("a"))),
            stored(payload = StageCompletedEvent(sessionId, StageId("a"), StageId("t1"))),
            stored(payload = OrchestrationPausedEvent(sessionId, StageId("a"), "hold")),
            stored(payload = OrchestrationResumedEvent(sessionId, StageId("a"))),
            stored(payload = StageCompletedEvent(sessionId, StageId("b"), StageId("t2"))),
            stored(payload = StageFailedEvent(sessionId, StageId("b"), StageId("t3"), "error")),
            stored(payload = WorkflowCompletedEvent(sessionId, StageId("c"), 3)),
        )

        var projected = state
        var reduced = state
        for (evt in events) {
            projected = projector.apply(projected, evt)
            reduced = reducer.reduce(reduced, evt)
        }
        assertEquals(reduced, projected)
    }

    @Test
    fun `full lifecycle through projector mirrors reducer pipeline`() {
        var state: TalkieState = projector.initial()

        state = projector.apply(
            state,
            stored(payload = WorkflowStartedEvent(sessionId, workflowId, StageId("stage-A"))),
        )
        assertEquals(WorkflowStatus.RUNNING, state.workflowStatus)

        state = projector.apply(
            state,
            stored(payload = StageCompletedEvent(sessionId, StageId("stage-A"), StageId("t1"))),
        )
        assertEquals(1, state.l2Memory.size)

        state = projector.apply(
            state,
            stored(payload = WorkflowCompletedEvent(sessionId, StageId("stage-A"), 1)),
        )
        assertEquals(WorkflowStatus.COMPLETED, state.workflowStatus)
        assertNull(state.currentStageId)
    }
}
