package link.socket.ampere.agents.events.relay

import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.datetime.Instant
import link.socket.ampere.agents.domain.event.Event
import link.socket.ampere.agents.domain.event.EventRegistry
import link.socket.ampere.agents.domain.event.EventType
import link.socket.ampere.agents.events.EventRepository
import link.socket.ampere.agents.events.api.EventHandler
import link.socket.ampere.agents.events.bus.EventSerialBus
import link.socket.ampere.agents.events.subscription.Subscription
import link.socket.ampere.agents.events.utils.generateUUID

/**
 * Default implementation of [EventRelayService] that uses [EventSerialBus] for live events
 * and [EventRepository] for historical replay.
 *
 * The implementation bridges the callback-based EventBus to Flow-based streams for
 * reactive consumption.
 */
class EventRelayServiceImpl(
    private val eventSerialBus: EventSerialBus,
    private val eventRepository: EventRepository,
) : EventRelayService {

    override fun subscribeToLiveEvents(filters: EventRelayFilters): Flow<Event> = callbackFlow {
        // If no filters, subscribe to all event types
        // Otherwise, subscribe to each filtered event type separately
        val eventTypes = filters.eventTypes ?: getAllKnownEventTypes()

        // Create subscriptions for each event type
        val subscriptions = eventTypes.map { eventType ->
            eventSerialBus.subscribe(
                agentId = "event-stream-${generateUUID()}",
                eventType = eventType,
                handler = EventHandler { event: Event, _: Subscription? ->
                    // Apply additional filtering beyond the event type
                    if (filters.matches(event)) {
                        trySend(event)
                    }
                },
            )
        }

        // Keep the flow alive until canceled
        awaitClose {
            // Unsubscribe from all event types when the flow is canceled
            eventTypes.forEach { eventType ->
                eventSerialBus.unsubscribe(eventType)
            }
        }
    }

    override suspend fun replayEvents(
        fromTime: Instant,
        toTime: Instant,
        filters: EventRelayFilters,
    ): Result<Flow<Event>> {
        // Extract filter values for database-level filtering
        val eventTypes = filters.eventTypes?.toSet()
        val sourceIds = filters.eventSources?.map { it.getIdentifier() }?.toSet()

        // Use the filtered query for database-level filtering when event types or source IDs are specified
        // Note: Urgency and eventId filters are still applied in-memory as they're not in the database schema
        return if (eventTypes != null || sourceIds != null) {
            eventRepository.getEventsWithFilters(
                fromTime = fromTime,
                toTime = toTime,
                eventTypes = eventTypes,
                sourceIds = sourceIds,
            ).map { events ->
                flow {
                    // Apply remaining filters (urgency, eventId) in-memory
                    events.forEach { event ->
                        if (filters.matches(event)) {
                            emit(event)
                        }
                    }
                }
            }
        } else {
            // No database-level filters, but might have urgency/eventId filters
            eventRepository.getEventsBetween(fromTime, toTime).map { events ->
                flow {
                    events.forEach { event ->
                        if (filters.matches(event)) {
                            emit(event)
                        }
                    }
                }
            }
        }
    }

    /**
     * Returns a set of all known event types in the system.
     * This is used when subscribing to all events (no filter).
     *
     * Uses EventRegistry as the single source of truth for all event types.
     */
    private fun getAllKnownEventTypes(): Set<EventType> =
        EventRegistry.allEventTypes.toSet()
}
