package com.cmvr.quic.gateway.core;

import com.cmvr.quic.contract.QuicEventBus;
import com.cmvr.quic.gateway.v1.MediaFrame;
import com.cmvr.quic.gateway.v1.NodeEvent;
import com.cmvr.quic.gateway.v1.NodeEventType;
import com.cmvr.quic.gateway.v1.NodeSnapshot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;

/** 保存 QUIC 节点状态，并在当前 JVM 内分发节点和媒体事件。 */
@Component
public final class NodeEventBroker implements QuicEventBus, AutoCloseable {

    private static final Logger log = LoggerFactory.getLogger(NodeEventBroker.class);

    private final ConcurrentMap<String, NodeSnapshot> nodes = new ConcurrentHashMap<String, NodeSnapshot>();
    private final CopyOnWriteArrayList<NodeWatcher> nodeWatchers = new CopyOnWriteArrayList<NodeWatcher>();
    private final CopyOnWriteArrayList<MediaSubscriber> mediaSubscribers =
            new CopyOnWriteArrayList<MediaSubscriber>();
    private final int defaultMediaQueueCapacity;
    private final AtomicBoolean closed = new AtomicBoolean(false);

    public NodeEventBroker(
            @Value("${cmvr.quic.media-subscriber-queue-capacity:64}") int defaultMediaQueueCapacity) {
        this.defaultMediaQueueCapacity = defaultMediaQueueCapacity;
    }

    @Override
    public Collection<NodeSnapshot> listNodes() {
        return new ArrayList<NodeSnapshot>(nodes.values());
    }

    public void publishNode(NodeEventType type, NodeSnapshot snapshot) {
        if (closed.get() || snapshot == null || snapshot.getNodeId().isEmpty()) {
            return;
        }
        if (!acceptNodeUpdate(type, snapshot)) {
            log.debug("忽略已被新连接替代的 QUIC 节点事件，nodeId={}，sessionId={}，type={}",
                    snapshot.getNodeId(), snapshot.getSessionId(), type);
            return;
        }
        NodeEvent event = NodeEvent.newBuilder()
                .setType(type)
                .setNode(snapshot)
                .setEventTimeUnixMs(System.currentTimeMillis())
                .build();
        for (NodeWatcher watcher : nodeWatchers) {
            if (!watcher.send(event)) {
                nodeWatchers.remove(watcher);
            }
        }
    }

    /** 同一 nodeId 重连后，旧连接迟到的心跳或离线事件不能覆盖新连接状态。 */
    private boolean acceptNodeUpdate(final NodeEventType type, final NodeSnapshot snapshot) {
        final AtomicBoolean accepted = new AtomicBoolean(false);
        nodes.compute(snapshot.getNodeId(), (nodeId, current) -> {
            if (type == NodeEventType.NODE_EVENT_TYPE_REGISTERED) {
                accepted.set(true);
                return snapshot;
            }
            if (current != null && current.getSessionId().equals(snapshot.getSessionId())) {
                accepted.set(true);
                return snapshot;
            }
            return current;
        });
        return accepted.get();
    }

    @Override
    public Subscription watchNodes(boolean includeCurrent, Consumer<NodeEvent> listener) {
        if (closed.get()) {
            return Subscription.CLOSED;
        }
        final NodeWatcher watcher = new NodeWatcher(listener);
        nodeWatchers.add(watcher);
        if (includeCurrent) {
            for (NodeSnapshot node : listNodes()) {
                NodeEvent event = NodeEvent.newBuilder()
                        .setType(node.getOnline() ? NodeEventType.NODE_EVENT_TYPE_HEARTBEAT
                                : NodeEventType.NODE_EVENT_TYPE_OFFLINE)
                        .setNode(node)
                        .setEventTimeUnixMs(System.currentTimeMillis())
                        .build();
                if (!watcher.send(event)) {
                    nodeWatchers.remove(watcher);
                    break;
                }
            }
        }
        return new Subscription() {
            @Override
            public void close() {
                nodeWatchers.remove(watcher);
                watcher.close();
            }
        };
    }

    @Override
    public Subscription subscribeMedia(String nodeId, int trackId, int requestedCapacity,
                                       Consumer<MediaFrame> listener) {
        if (closed.get()) {
            return Subscription.CLOSED;
        }
        int capacity = requestedCapacity <= 0 ? defaultMediaQueueCapacity
                : Math.max(1, Math.min(1024, requestedCapacity));
        final MediaSubscriber subscriber = new MediaSubscriber(nodeId, trackId, capacity, listener);
        mediaSubscribers.add(subscriber);
        subscriber.start();
        return new Subscription() {
            @Override
            public void close() {
                mediaSubscribers.remove(subscriber);
                subscriber.close();
            }
        };
    }

    public void publishMedia(MediaFrame frame) {
        if (closed.get()) {
            return;
        }
        for (MediaSubscriber subscriber : mediaSubscribers) {
            if (subscriber.matches(frame)) {
                subscriber.offer(frame);
            }
        }
    }

    @Override
    public void close() {
        if (!closed.compareAndSet(false, true)) {
            return;
        }
        for (NodeWatcher watcher : nodeWatchers) {
            watcher.close();
        }
        nodeWatchers.clear();
        for (MediaSubscriber subscriber : mediaSubscribers) {
            subscriber.close();
        }
        mediaSubscribers.clear();
        nodes.clear();
    }

    private static final class NodeWatcher {
        private final Consumer<NodeEvent> listener;
        private boolean closed;

        private NodeWatcher(Consumer<NodeEvent> listener) {
            if (listener == null) {
                throw new IllegalArgumentException("节点事件监听器不能为空");
            }
            this.listener = listener;
        }

        private synchronized boolean send(NodeEvent event) {
            if (closed) {
                return false;
            }
            try {
                listener.accept(event);
                return true;
            } catch (RuntimeException error) {
                closed = true;
                log.warn("QUIC 节点事件监听器执行失败，订阅已关闭", error);
                return false;
            }
        }

        private synchronized void close() {
            closed = true;
        }
    }

    private final class MediaSubscriber implements Runnable {
        private final String nodeId;
        private final int trackId;
        private final ArrayBlockingQueue<MediaFrame> queue;
        private final Consumer<MediaFrame> listener;
        private final AtomicBoolean closed = new AtomicBoolean(false);
        private final AtomicLong dropped = new AtomicLong();
        private Thread worker;

        private MediaSubscriber(String nodeId, int trackId, int capacity,
                                Consumer<MediaFrame> listener) {
            if (listener == null) {
                throw new IllegalArgumentException("媒体事件监听器不能为空");
            }
            this.nodeId = nodeId == null ? "" : nodeId;
            this.trackId = trackId;
            this.queue = new ArrayBlockingQueue<MediaFrame>(capacity);
            this.listener = listener;
        }

        private boolean matches(MediaFrame frame) {
            return (nodeId.isEmpty() || nodeId.equals(frame.getNodeId()))
                    && (trackId == 0 || trackId == frame.getTrackId());
        }

        private void start() {
            worker = new Thread(this, "quic-media-subscriber-" + Integer.toHexString(hashCode()));
            worker.setDaemon(true);
            worker.start();
        }

        private void offer(MediaFrame frame) {
            if (closed.get() || queue.offer(frame)) {
                return;
            }
            queue.poll();
            if (!queue.offer(frame)) {
                return;
            }
            long count = dropped.incrementAndGet();
            if (count == 1 || count % 100 == 0) {
                log.warn("QUIC 媒体订阅消费过慢，已丢弃最旧帧，nodeId={}，trackId={}，dropped={}",
                        nodeId, trackId, count);
            }
        }

        @Override
        public void run() {
            try {
                while (!closed.get()) {
                    MediaFrame frame = queue.poll(500, TimeUnit.MILLISECONDS);
                    if (frame != null) {
                        listener.accept(frame);
                    }
                }
            } catch (InterruptedException interrupted) {
                Thread.currentThread().interrupt();
            } catch (RuntimeException error) {
                log.warn("QUIC 媒体订阅监听器执行失败，订阅已关闭", error);
            } finally {
                closed.set(true);
                queue.clear();
                mediaSubscribers.remove(this);
            }
        }

        private void close() {
            if (!closed.compareAndSet(false, true)) {
                return;
            }
            if (worker != null) {
                worker.interrupt();
            }
            queue.clear();
        }
    }
}
