package com.correx.apps.server.bridge

import com.correx.apps.server.protocol.EventEntryDto
import com.correx.apps.server.protocol.ServerMessage
import com.correx.apps.server.registry.WorkflowRegistry
import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.OrchestrationResumedEvent
import com.correx.core.events.events.StageCompletedEvent
import com.correx.core.events.events.StoredEvent
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.orchestration.OrchestrationState
import com.correx.core.events.orchestration.OrchestrationStatus
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.ArtifactId
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.kernel.orchestration.OrchestrationRepository
import com.correx.core.tools.contract.Tool
import com.correx.core.tools.registry.ToolRegistry
import com.correx.core.sessions.projections.replay.EventReplayer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class SessionEventBridgeTest {

    private val sessionId = SessionId("session-1")
    private val stageId = StageId("stage-1")
    private val workflowId = "workflow-test"
    private val timestamp = Instant.parse("2026-01-01T00:00:00Z")

    private val noopArtifactStore: ArtifactStore = object : ArtifactStore {
        override suspend fun put(bytes: ByteArray): ArtifactId = ArtifactId("art-1")
        override suspend fun get(id: ArtifactId): ByteArray? = null
        override suspend fun flushBefore(commit: suspend () -> Unit) = commit()
    }

    private fun storedEvent(payload: EventPayload, seq: Long = 1L): StoredEvent =
        StoredEvent(
            metadata = EventMetadata(
                eventId = EventId("evt-$seq"),
                sessionId = sessionId,
                timestamp = timestamp,
                schemaVersion = 1,
                causationId = null,
                correlationId = null,
            ),
            sequence = seq,
            sessionSequence = seq,
            payload = payload,
        )

    private fun fakeEventStore(
        allEventsList: List<StoredEvent> = emptyList(),
        liveFlow: Flow<StoredEvent> = flow {},
    ): EventStore = object : EventStore {
        override suspend fun append(event: NewEvent): StoredEvent = error("unused")
        override suspend fun appendAll(events: List<NewEvent>): List<StoredEvent> = error("unused")
        override fun read(sessionId: SessionId): List<StoredEvent> = allEventsList
        override fun readFrom(sessionId: SessionId, fromSequence: Long): List<StoredEvent> = allEventsList
        override fun lastSequence(sessionId: SessionId): Long =
            allEventsList.maxOfOrNull { it.sequence } ?: 0L

        override fun subscribe(sessionId: SessionId): Flow<StoredEvent> = liveFlow
        override fun allEvents(): Sequence<StoredEvent> = allEventsList.asSequence()
        override fun allSessionIds(): Set<SessionId> = allEventsList.map { it.metadata.sessionId }.toSet()
        override fun subscribeAll(): Flow<StoredEvent> = TODO("Not needed in this test context")
        override suspend fun lastGlobalSequence(): Long =
            allEventsList.maxOfOrNull { it.sequence } ?: 0L
    }

    private fun activeOrchestrationRepository(
        status: OrchestrationStatus = OrchestrationStatus.RUNNING,
    ): OrchestrationRepository = OrchestrationRepository(
        object : EventReplayer<OrchestrationState> {
            override fun rebuild(sessionId: SessionId): OrchestrationState = OrchestrationState(
                workflowId = workflowId,
                status = status,
                currentStageId = stageId,
                pendingApproval = false,
            )
        },
    )

    private val noopWorkflowRegistry = object : WorkflowRegistry {
        override fun listAll(): List<com.correx.apps.server.registry.WorkflowSummary> = emptyList()
        override fun find(workflowId: String): com.correx.core.transitions.graph.WorkflowGraph? = null
    }

    private val noopToolRegistry = object : ToolRegistry {
        override fun resolve(name: String): Tool? = null
        override fun all(): List<Tool> = emptyList()
    }

    @Test
    fun `replaySnapshot sends mapped messages for all events`() = runTest {
        val events = listOf(
            storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L),
            storedEvent(WorkflowCompletedEvent(sessionId, stageId, 1), seq = 2L),
        )
        val store = fakeEventStore(allEventsList = events)
        val orchRepo = activeOrchestrationRepository()
        val sent = mutableListOf<ServerMessage>()
        val bridge = SessionEventBridge(
            store,
            noopArtifactStore,
            orchRepo,
            noopWorkflowRegistry,
            noopToolRegistry,
        ) { sent.add(it) }

        bridge.replaySnapshot()

        assertEquals(2, sent.size)
        val snapshot = sent[0] as ServerMessage.SessionSnapshot
        assertEquals(sessionId, snapshot.sessionId)
        assertEquals(2L, snapshot.lastSequence)
        assertEquals(2L, snapshot.lastSessionSequence)
        assertEquals(ServerMessage.SnapshotComplete, sent[1])
    }

    @Test
    fun `replaySnapshot populates recentEvents from event store`() = runTest {
        val events = listOf(
            storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L),
            storedEvent(TransitionExecutedEvent(sessionId, stageId, StageId("stage-2"), TransitionId("t1")), seq = 2L),
            storedEvent(StageCompletedEvent(sessionId, stageId, TransitionId("t2")), seq = 3L),
            storedEvent(WorkflowCompletedEvent(sessionId, stageId, 1), seq = 4L),
        )
        val store = fakeEventStore(allEventsList = events)
        val orchRepo = activeOrchestrationRepository(status = OrchestrationStatus.COMPLETED)
        val sent = mutableListOf<ServerMessage>()
        val bridge = SessionEventBridge(
            store,
            noopArtifactStore,
            orchRepo,
            noopWorkflowRegistry,
            noopToolRegistry,
        ) { sent.add(it) }

        bridge.replaySnapshot()

        assertEquals(2, sent.size)
        val snapshot = sent[0] as ServerMessage.SessionSnapshot
        // WorkflowStartedEvent is filtered out by eventToEntry (maps to null)
        assertEquals(3, snapshot.recentEvents.size)
        assertEquals(EventEntryDto(timestamp.toEpochMilliseconds(), "StageStarted", "stage-2"), snapshot.recentEvents[0])
        assertEquals(EventEntryDto(timestamp.toEpochMilliseconds(), "StageCompleted", stageId.value), snapshot.recentEvents[1])
        assertEquals(EventEntryDto(timestamp.toEpochMilliseconds(), "SessionCompleted", ""), snapshot.recentEvents[2])
    }

    @Test
    fun `replaySnapshot skips unmapped events`() = runTest {
        val events = listOf(
            storedEvent(OrchestrationResumedEvent(sessionId, stageId), seq = 1L),
            storedEvent(WorkflowCompletedEvent(sessionId, stageId, 1), seq = 2L),
        )
        val store = fakeEventStore(allEventsList = events)
        val orchRepo = activeOrchestrationRepository()
        val sent = mutableListOf<ServerMessage>()
        val bridge = SessionEventBridge(
            store,
            noopArtifactStore,
            orchRepo,
            noopWorkflowRegistry,
            noopToolRegistry,
        ) { sent.add(it) }

        bridge.replaySnapshot()

        assertEquals(2, sent.size)
        val snapshot = sent[0] as ServerMessage.SessionSnapshot
        assertEquals(sessionId, snapshot.sessionId)
        assertEquals(ServerMessage.SnapshotComplete, sent[1])
    }

    @Test
    fun `replaySnapshot with no sessions emits only SnapshotComplete`() = runTest {
        val store = fakeEventStore(allEventsList = emptyList())
        val sent = mutableListOf<ServerMessage>()
        val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
        bridge.replaySnapshot()
        assertEquals(1, sent.size)
        assertEquals(ServerMessage.SnapshotComplete, sent[0])
    }

    @Test
    fun `replaySnapshot emits SnapshotComplete as final message`() = runTest {
        val events = listOf(storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L))
        val store = fakeEventStore(allEventsList = events)
        val sent = mutableListOf<ServerMessage>()
        val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
        bridge.replaySnapshot()
        assertEquals(ServerMessage.SnapshotComplete, sent.last())
    }

    @Test
    fun `replaySnapshot lastSequence reflects global cursor before projection read`() = runTest {
        val events = listOf(
            storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L),
            storedEvent(WorkflowCompletedEvent(sessionId, stageId, 1), seq = 2L),
        )
        val store = object : EventStore by fakeEventStore(allEventsList = events) {
            override suspend fun lastGlobalSequence(): Long = 5L
        }
        val sent = mutableListOf<ServerMessage>()
        val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
        bridge.replaySnapshot()
        val snapshot = sent[0] as ServerMessage.SessionSnapshot
        assertEquals(5L, snapshot.lastSequence)
    }

    @Test
    fun `replaySnapshot lastSessionSequence equals per-session cursor`() = runTest {
        val events = listOf(
            storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L),
            storedEvent(WorkflowCompletedEvent(sessionId, stageId, 1), seq = 3L),
        )
        val store = fakeEventStore(allEventsList = events)
        val sent = mutableListOf<ServerMessage>()
        val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
        bridge.replaySnapshot()
        val snapshot = sent[0] as ServerMessage.SessionSnapshot
        assertEquals(3L, snapshot.lastSessionSequence)
    }

}
