package com.correx.core.talkie

import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.IdeaCapturedEvent
import com.correx.core.events.events.IdeaDiscardedEvent
import com.correx.core.events.events.IdeaPromotedEvent
import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class IdeaReaderTest {

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

    @Test
    fun `a promoted idea is filtered out of the board like a discard`() {
        val store = fakeStore(
            IdeaCapturedEvent("keep", session, "cache the repo map", timestampMs = 1),
            IdeaCapturedEvent("promoted", session, "no bare try-catch", timestampMs = 2),
            IdeaCapturedEvent("discarded", session, "old note", timestampMs = 3),
            IdeaPromotedEvent("promoted", session, "no bare try-catch", timestampMs = 99),
            IdeaDiscardedEvent("discarded", session),
        )

        val active = IdeaReader(store).activeIdeas()

        assertEquals(listOf("keep"), active.map { it.id })
        assertFalse(active.any { it.id == "promoted" }, "promoted idea must leave the board")
        assertFalse(active.any { it.id == "discarded" }, "discarded idea must leave the board")
    }

    @Test
    fun `textOf returns the captured text and null for an unknown idea`() {
        val store = fakeStore(
            IdeaCapturedEvent("i1", session, "events are the only source of truth", timestampMs = 1),
        )
        val reader = IdeaReader(store)

        assertEquals("events are the only source of truth", reader.textOf("i1"))
        assertNull(reader.textOf("missing"))
    }

    @Test
    fun `textOf still resolves the text after the idea is promoted`() {
        val store = fakeStore(
            IdeaCapturedEvent("i1", session, "prefer data classes", timestampMs = 1),
            IdeaPromotedEvent("i1", session, "prefer data classes", timestampMs = 5),
        )
        val reader = IdeaReader(store)

        // The capture stays in the log (invariant #1), so the text remains readable.
        assertEquals("prefer data classes", reader.textOf("i1"))
        assertTrue(reader.activeIdeas().isEmpty())
    }

    private fun fakeStore(vararg payloads: EventPayload): EventStore = FakeEventStore(payloads.toList())

    /** Minimal read-only [EventStore]: only [allEvents] is exercised by [IdeaReader]. */
    private class FakeEventStore(payloads: List<EventPayload>) : EventStore {
        private val stored: List<StoredEvent> = payloads.mapIndexed { index, payload ->
            StoredEvent(
                metadata = EventMetadata(
                    eventId = EventId("e-$index"),
                    sessionId = SessionId("s-1"),
                    timestamp = Instant.fromEpochMilliseconds(index.toLong()),
                    schemaVersion = 1,
                    causationId = null,
                    correlationId = null,
                ),
                sequence = index.toLong(),
                sessionSequence = index.toLong(),
                payload = payload,
            )
        }

        override fun allEvents(): Sequence<StoredEvent> = stored.asSequence()

        override suspend fun append(event: NewEvent): StoredEvent = throw NotImplementedError()
        override suspend fun appendAll(events: List<NewEvent>): List<StoredEvent> = throw NotImplementedError()
        override fun read(sessionId: SessionId): List<StoredEvent> = throw NotImplementedError()
        override fun readFrom(sessionId: SessionId, fromSequence: Long): List<StoredEvent> =
            throw NotImplementedError()
        override fun lastSequence(sessionId: SessionId): Long? = throw NotImplementedError()
        override fun subscribe(sessionId: SessionId): Flow<StoredEvent> = emptyFlow()
        override fun subscribeAll(): Flow<StoredEvent> = emptyFlow()
        override suspend fun lastGlobalSequence(): Long = throw NotImplementedError()
        override fun allSessionIds(): Set<SessionId> = throw NotImplementedError()
    }
}
