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

import de.accso.flexinale.common.api.eventbus.EventBus;
import de.accso.flexinale.common.api.eventbus.EventContextHolder;
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.DeveloperMistakeException;
import de.accso.flexinale.common.shared_kernel.Selection;
import de.accso.flexinale.common.shared_kernel.SelectionMultiMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Set;

@SuppressWarnings("unused")
public class InMemorySyncEventBus<E extends Event> implements EventBus<E> {
    private static final Logger LOGGER = LoggerFactory.getLogger(InMemorySyncEventBus.class);

    protected record SubscriberGroup(String consumerGroupName) {
        static SubscriberGroup of(EventSubscriber<?> subscriber) {
            return new SubscriberGroup(subscriber.getGroupName());
        }
    }

    protected final Class<E> eventType;

    protected static final EventContextHolder eventContextHolder = new InMemorySyncEventContextHolder();

    protected final EventSubscriberReceiveDispatcher<E>            subscriberReceiveDispatcher = new EventSubscriberReceiveDispatcher<>();
    protected final EventSubscriberReceiveDispatcher<Event> genericSubscriberReceiveDispatcher = new EventSubscriberReceiveDispatcher<>();

    protected final SelectionMultiMap<SubscriberGroup, EventSubscriber<E>> subscribersPerGroupUsingSelection =
            new SelectionMultiMap<>(Selection.SelectionAlgorithm.ROUND_ROBIN);
    protected final SelectionMultiMap<SubscriberGroup, EventSubscriber<Event>> genericSubscribersPerGroupUsingSelection =
            new SelectionMultiMap<>(Selection.SelectionAlgorithm.ROUND_ROBIN);

    InMemorySyncEventBus(final Class<E> eventType) {
        this.eventType = eventType;
    }

    @Override
    public EventContextHolder getEventContextHolder() {
        return eventContextHolder;
    }

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

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

        handle_SubscriptionAtStart(eventSubscriptionAtStart);
    }

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

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

        handle_SubscriptionAtStart(eventSubscriptionAtStart);
    }

    private void handle_SubscriptionAtStart(final EventSubscriptionAtStart eventSubscriptionAtStart) {
        switch (eventSubscriptionAtStart) {
            case START_READING_FROM_BEGINNING, START_READING_FROM_LAST_TIME -> {
                throw new DeveloperMistakeException("not implemented. Probably want to use InMemorySyncEventBusSpy");
            }
            case START_READING_FROM_NOW -> {
                // nope, nothing to do
            }
        }
    }

    @Override
    public void unsubscribe(final Class<E> eventType, final EventSubscriber<E> subscriber) {
        LOGGER.debug("InMemorySyncEventBus<%s>: subscriber %s unregistered".formatted(eventType.getSimpleName(), subscriber));

        subscribersPerGroupUsingSelection.removeMapping(SubscriberGroup.of(subscriber), subscriber);
    }

    @Override
    public void unsubscribe(EventSubscriber<Event> genericSubscriber) {
        LOGGER.debug("InMemorySyncEventBus<%s>: subscriber %s unregistered".formatted(eventType.getSimpleName(), genericSubscriber));

        genericSubscribersPerGroupUsingSelection.removeMapping(SubscriberGroup.of(genericSubscriber), genericSubscriber);
    }

    @Override
    public void unsubscribeAll() {
        LOGGER.debug("InMemorySyncEventBus<%s>: all subscribers and generic subscribers unregistered".formatted(eventType.getSimpleName()));

        subscribersPerGroupUsingSelection.clear();
        genericSubscribersPerGroupUsingSelection.clear();
    }

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

        // publish event to subscribers (depending on the selection algorithm, e.g. RoundRobin)
        try {
            eventContextHolder.set(event.eventContext());

            // for each group of the subscribers, select one (or more) subscribers, using the Selection algorithm
            subscribersPerGroupUsingSelection.keySet()
                    .forEach(subscriberGroup -> {
                        Selection<EventSubscriber<E>> allSubscribersInGroup =
                                (Selection<EventSubscriber<E>>) subscribersPerGroupUsingSelection.get(subscriberGroup);
                        Set<EventSubscriber<E>> subscribersToSendTo = allSubscribersInGroup.next();
                        subscribersToSendTo.forEach(subscriberToSendTo ->
                                internal_sendToSubscriber(eventType, event, subscriberToSendTo));
                    });

            // for each group of the generic subscribers, select one (or more) subscribers, using the Selection algorithm
            genericSubscribersPerGroupUsingSelection.keySet()
                    .forEach(subscriberGroup -> {
                        Selection<EventSubscriber<Event>> allGenericSubscribersInGroup =
                                (Selection<EventSubscriber<Event>>) genericSubscribersPerGroupUsingSelection.get(subscriberGroup);
                        Set<EventSubscriber<Event>> genericSubscribersToSendTo = allGenericSubscribersInGroup.next();
                        genericSubscribersToSendTo.forEach(genericSubscriberToSendTo ->
                                internal_sendToGenericSubscriber(event, genericSubscriberToSendTo));
                    });
        }
        finally {
            eventContextHolder.remove();
        }
    }

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

    // send event to one subscriber
    protected void internal_sendToSubscriber(Class<E> eventType, E event, EventSubscriber<E> subscriber) {
        subscriberReceiveDispatcher.callSubscriberReceiveMethod(eventType, event, subscriber);
    }

    // send event to one generic subscriber
    protected void internal_sendToGenericSubscriber(E event, EventSubscriber<Event> subscriber) {
        genericSubscriberReceiveDispatcher.callSubscriberReceiveMethod(Event.class, event, subscriber);
    }
}
