package ru.dragonestia.kubik.discovery.component;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import ru.dragonestia.kubik.discovery.event.ServerRegisterEvent;
import ru.dragonestia.kubik.discovery.event.ServerUnregisterEvent;
import ru.dragonestia.kubik.discovery.service.ServerService;
import ru.dragonestia.kubik.dto.KubikEventUpdate;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

@Log4j2
@RequiredArgsConstructor
@Component
public class UpdateStateNotifier {

    private final ServerService serverService;
    private final Map<UUID, Subscriber> subscribers = new ConcurrentHashMap<>();

    @EventListener
    void onRegisterServer(ServerRegisterEvent event) {
        var changes = new KubikEventUpdate(
                new HashSet<>(),
                new HashSet<>(List.of(new KubikEventUpdate.Entry(event.id(), event.address(), event.port()))),
                false
        );
        subscribers.values().forEach(subscriber -> subscriber.update(changes));
    }

    @EventListener
    void onUnregisterServer(ServerUnregisterEvent event) {
        var changes = new KubikEventUpdate(
                new HashSet<>(List.of(event.id())),
                new HashSet<>(),
                false
        );
        subscribers.values().forEach(subscriber -> subscriber.update(changes));
    }

    public Subscriber subscribe(Consumer<Object> valueConsumer) {
        synchronized (UpdateStateNotifier.class) {
            var subscriber = new Subscriber(this, valueConsumer);
            subscriber.update(initValues());
            subscribers.put(subscriber.uuid, subscriber);
            return subscriber;
        }
    }

    public Object initValues() {
        return new KubikEventUpdate(
                new HashSet<>(),
                new HashSet<>(serverService.getAllServers().stream()
                        .map(server -> new KubikEventUpdate.Entry(server.getId(), server.getAddress(), server.getPort()))
                        .toList()),
                true);
    }

    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    public static class Subscriber {

        private final UUID uuid = UUID.randomUUID();

        private final UpdateStateNotifier manager;
        private final Consumer<Object> consumer;

        private boolean disposed = false;

        public void update(Object response) {
            try {
                consumer.accept(response);
            } catch (Exception ex) {
                log.error(ex.getMessage(), ex);
                dispose();
            }
        }

        public void dispose() {
            synchronized (UpdateStateNotifier.class) {
                if (disposed) return;
                disposed = true;

                manager.subscribers.remove(uuid);
            }
        }
    }
}
