package com.correx.infrastructure.persistence

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.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import org.slf4j.LoggerFactory
import java.util.concurrent.*
import java.util.concurrent.atomic.*

private val log = LoggerFactory.getLogger(InMemoryEventStore::class.java)

class InMemoryEventStore : EventStore {
    private val streams = ConcurrentHashMap<SessionId, MutableList<StoredEvent>>()
    private val sequences = ConcurrentHashMap<SessionId, AtomicLong>()
    private val globalSeq = AtomicLong(0)
    private val seenEventIds = ConcurrentHashMap.newKeySet<EventId>()
    private val subscriptions = ConcurrentHashMap<SessionId, MutableSharedFlow<StoredEvent>>()
    private val globalFlow = MutableSharedFlow<StoredEvent>(
        replay = 0,
        extraBufferCapacity = 1024,
        onBufferOverflow = BufferOverflow.SUSPEND,
    )

    override suspend fun append(event: NewEvent): StoredEvent {
        val stream = streams.computeIfAbsent(event.metadata.sessionId) { mutableListOf() }
        val stored: StoredEvent
        synchronized(stream) {
            if (!seenEventIds.add(event.metadata.eventId)) {
                error("duplicate event_id: ${event.metadata.eventId}")
            }
            stored = doAppend(event, stream)
        }
        subscriptions[event.metadata.sessionId]?.let { warnIfDropped(it.tryEmit(stored), stored) }
        globalFlow.emit(stored)
        return stored
    }

    override suspend fun appendAll(events: List<NewEvent>): List<StoredEvent> {
        if (events.isEmpty()) return emptyList()
        val sessionId = events.first().metadata.sessionId
        val stream = streams.computeIfAbsent(sessionId) { mutableListOf() }
        val stored: List<StoredEvent>
        synchronized(stream) {
            stored = events.map { doAppend(it, stream) }
        }
        val flow = subscriptions[sessionId]
        if (flow != null) stored.forEach { warnIfDropped(flow.tryEmit(it), it) }
        stored.forEach { globalFlow.emit(it) }
        return stored
    }

    override fun read(sessionId: SessionId): List<StoredEvent> =
        streams[sessionId]?.sortedBy { it.sessionSequence } ?: emptyList()

    override fun readFrom(sessionId: SessionId, fromSequence: Long): List<StoredEvent> =
        streams[sessionId]
            ?.filter { it.sessionSequence > fromSequence }
            ?.toList()
            ?: emptyList()

    override fun lastSequence(sessionId: SessionId): Long? = sequences[sessionId]?.get()

    override fun subscribe(sessionId: SessionId): Flow<StoredEvent> =
        subscriptions.computeIfAbsent(sessionId) { MutableSharedFlow(replay = 0, extraBufferCapacity = 64) }

    override fun subscribeAll(): Flow<StoredEvent> = globalFlow.asSharedFlow()

    override suspend fun lastGlobalSequence(): Long = globalSeq.get()

    override fun allEvents(): Sequence<StoredEvent> =
        streams.values
            .flatMap { stream -> synchronized(stream) { stream.toList() } }
            .asSequence()

    override fun allSessionIds(): Set<SessionId> = sequences.keys

    /** A lagging per-session collector's buffer (extraBufferCapacity 64) is full; the event is durably
     *  stored but dropped from the live SharedFlow. Surface it rather than swallow the false return. */
    private fun warnIfDropped(emitted: Boolean, event: StoredEvent) {
        if (!emitted) {
            log.warn(
                "dropped live subscription emit for session {} seq {} ({}): buffer full, collector lagging",
                event.metadata.sessionId.value,
                event.sequence,
                event.payload::class.simpleName,
            )
        }
    }

    private fun doAppend(event: NewEvent, stream: MutableList<StoredEvent>): StoredEvent {
        val sessionSeq = sequences.computeIfAbsent(event.metadata.sessionId) { AtomicLong(0) }
            .incrementAndGet()
        val globalSeqVal = globalSeq.incrementAndGet()

        val stored = StoredEvent(
            metadata = event.metadata,
            sequence = globalSeqVal,
            sessionSequence = sessionSeq,
            payload = event.payload,
        )
        if (stream.isNotEmpty() && stream.last().sessionSequence >= stored.sessionSequence) {
            error("sequence violation for session ${event.metadata.sessionId}")
        }
        stream.add(stored)
        return stored
    }
}
