package de.accso.flexinale.common.infrastructure.eventbus;

import de.accso.flexinale.common.api.eventbus.EventSubscriber;
import de.accso.flexinale.common.api.eventbus.EventSubscriptionAtStart;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.ThreadSafeCounterMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

public class InMemorySyncEventBusSpy<E extends Event> extends InMemorySyncEventBus<E> {
    private static final Logger LOGGER = LoggerFactory.getLogger(InMemorySyncEventBusSpy.class);

    // localEventQueue is used as an in-memory storage and in tests as a spy
    private final ConcurrentLinkedQueue<E> localEventQueue = new ConcurrentLinkedQueue<>(); // TODO is currently never cleared - might want to use Cache implementation with automatic time-to-live?

    private final ThreadSafeCounterMap<SubscriberGroup> consumerGroupOffsetMap = new ThreadSafeCounterMap<>();

    InMemorySyncEventBusSpy(final Class<E> eventType) {
        super(eventType);
    }

    public List<E> allEvents() {
        return localEventQueue.stream().toList();
    }

    public void clear() {
        localEventQueue.clear();
    }

    public int size() {
        return localEventQueue.size();
    }

    // ----------------------------------------------------------------------------------------------------------------

    @Override
    public void subscribe(final Class<E> eventType, final EventSubscriber<E> subscriber,
                          final EventSubscriptionAtStart eventSubscriptionAtStart) {
        LOGGER.debug("InMemorySyncEventBusSpy<%s>: subscriber %s registered".formatted(eventType.getSimpleName(), subscriber));

        subscribersPerGroupUsingSelection.put(SubscriberGroup.of(subscriber), subscriber);

        handle_SubscriptionAtStart(eventType, subscriber, eventSubscriptionAtStart);
    }

    private void handle_SubscriptionAtStart(final Class<E> eventType,
                                            final EventSubscriber<E> subscriber,
                                            final EventSubscriptionAtStart eventSubscriptionAtStart) {
        switch (eventSubscriptionAtStart) {
            case START_READING_FROM_BEGINNING -> {
                // re-publish all events from beginning
                localEventQueue.forEach(event -> this.internal_sendToSubscriber(eventType, event, subscriber));
            }
            case START_READING_FROM_NOW -> {
                // nope, nothing to do
            }
            case START_READING_FROM_LAST_TIME -> {
                long offset = consumerGroupOffsetMap.get(SubscriberGroup.of(subscriber));

                // includes the case, that a consumer group was not known before -
                //    as then offset from the map is 0
                //    so nothing skipped but same behaviour as START_READING_FROM_BEGINNING
                localEventQueue.stream().
                        skip(offset).
                        forEach(event -> this.internal_sendToSubscriber(eventType, event, subscriber));
            }
        }
    }

    @Override
    public void subscribe(EventSubscriber<Event> genericSubscriber,
                          final EventSubscriptionAtStart eventSubscriptionAtStart) {
        LOGGER.debug("InMemorySyncEventBusSpy<%s>: generic subscriber %s registered".formatted(eventType.getSimpleName(), genericSubscriber));

        genericSubscribersPerGroupUsingSelection.put(SubscriberGroup.of(genericSubscriber), genericSubscriber);

        handle_SubscriptionAtStart(genericSubscriber, eventSubscriptionAtStart);
    }

    private void handle_SubscriptionAtStart(final EventSubscriber<Event> genericSubscriber,
                                            final EventSubscriptionAtStart eventSubscriptionAtStart) {
        switch (eventSubscriptionAtStart) {
            case START_READING_FROM_BEGINNING -> {
                // re-publish all events from beginning
                localEventQueue.forEach(event -> this.internal_sendToGenericSubscriber(event, genericSubscriber));
            }
            case START_READING_FROM_NOW -> {
                // nope, nothing to do
            }
            case START_READING_FROM_LAST_TIME -> {
                long offset = consumerGroupOffsetMap.get(SubscriberGroup.of(genericSubscriber));

                // includes the case, that a consumer group was not known before, ie. offset is 0,
                //    so nothing skipped but same behaviour as START_READING_FROM_BEGINNING
                localEventQueue.stream().
                        skip(offset).
                        forEach(event -> this.internal_sendToGenericSubscriber(event, genericSubscriber));
            }
        }
    }

    @Override
    public void publish(final Class<E> eventType, final E event) {
        LOGGER.info("InMemorySyncEventBusSpy<%s>: publish new event %s".formatted(eventType, event));

        super.publish(eventType, event);

        // persist event in local (in-memory) storage (i.e. spy)
        localEventQueue.add(event);
    }

    // -----------------------------------------------------------------------------------------------------------------

    // send event to one subscriber
    @Override
    protected void internal_sendToSubscriber(final Class<E> eventType, final E event, final EventSubscriber<E> subscriber) {
        subscriberReceiveDispatcher.callSubscriberReceiveMethod(eventType, event, subscriber);
        consumerGroupOffsetMap.increment(SubscriberGroup.of(subscriber));
    }

    // send event to one generic subscriber
    @Override
    protected void internal_sendToGenericSubscriber(final E event, final EventSubscriber<Event> genericSubscriber) {
        genericSubscriberReceiveDispatcher.callSubscriberReceiveMethod(Event.class, event, genericSubscriber);
        consumerGroupOffsetMap.increment(SubscriberGroup.of(genericSubscriber));
    }
}
