package com.lingniu.ingest.core.concurrency;

import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.api.sink.EventSink;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.BusySpinWaitStrategy;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 基于 Disruptor 的事件总线：Dispatcher 投递事件 → RingBuffer → Sink 扇出。
 *
 * <p>使用虚拟线程作为 Sink 消费线程，避免阻塞 IO 占用平台线程。
 * 单 VIN 有序性由上游 Netty + 分区投递保证，RingBuffer 不做 per-vin 排序。
 */
public final class DisruptorEventBus implements AutoCloseable {

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

    private final Disruptor<VehicleEventSlot> disruptor;
    private final List<EventSink> sinks;
    private final ExecutorService awaitExecutor;
    private final AtomicLong published = new AtomicLong();
    private final AtomicLong failed = new AtomicLong();

    public DisruptorEventBus(int ringBufferSize, String waitStrategyName, List<EventSink> sinks) {
        this.sinks = List.copyOf(sinks);
        this.awaitExecutor = Executors.newVirtualThreadPerTaskExecutor();
        ThreadFactory tf = Thread.ofVirtual().name("ingest-bus-", 0).factory();
        this.disruptor = new Disruptor<>(
                VehicleEventSlot::new,
                ringBufferSize,
                tf,
                ProducerType.MULTI,
                waitStrategy(waitStrategyName));

        EventHandler<VehicleEventSlot>[] handlers = this.sinks.stream()
                .map(this::toHandler)
                .toArray(EventHandler[]::new);
        // 每个 sink 一个独立 handler，事件按扇出模式同时写 Kafka、archive、历史索引等目标。
        disruptor.handleEventsWith(handlers);
        disruptor.start();
        log.info("DisruptorEventBus started ringBuffer={} wait={} sinks={}",
                ringBufferSize, waitStrategyName, this.sinks.size());
    }

    public void publish(VehicleEvent event) {
        disruptor.getRingBuffer().publishEvent((slot, seq, e) -> slot.event = e, event);
        published.incrementAndGet();
    }

    public CompletableFuture<Void> publishAndAwait(VehicleEvent event, String requiredSinkName) {
        String requiredSink = Objects.requireNonNull(requiredSinkName, "requiredSinkName");
        published.incrementAndGet();
        List<EventSink> acceptingSinks = sinks.stream()
                .filter(sink -> sink.accepts(event))
                .toList();
        List<EventSink> requiredSinks = acceptingSinks.stream()
                .filter(sink -> requiredSink.equals(sink.name()))
                .toList();
        if (requiredSinks.isEmpty()) {
            return CompletableFuture.failedFuture(new IllegalStateException(
                    "required sink '" + requiredSink + "' did not accept event " + event.eventId()));
        }

        List<CompletableFuture<Void>> requiredFutures = new ArrayList<>();
        for (EventSink sink : acceptingSinks) {
            CompletableFuture<Void> future = CompletableFuture
                    .supplyAsync(() -> publishToSinkAndTrack(sink, event), awaitExecutor)
                    .thenCompose(f -> f);
            if (requiredSink.equals(sink.name())) {
                requiredFutures.add(future);
            }
        }
        return CompletableFuture.allOf(requiredFutures.toArray(CompletableFuture[]::new));
    }

    @Override
    public void close() {
        disruptor.shutdown();
        awaitExecutor.shutdown();
        log.info("DisruptorEventBus stopped published={} failed={}", published.get(), failed.get());
    }

    private EventHandler<VehicleEventSlot> toHandler(EventSink sink) {
        return (slot, seq, endOfBatch) -> {
            VehicleEvent e = slot.event;
            if (e == null || !sink.accepts(e)) return;
            try {
                publishToSinkAndTrack(sink, e);
            } catch (Throwable t) {
                failed.incrementAndGet();
                log.warn("sink {} publish failed", sink.name(), t);
            }
        };
    }

    private CompletableFuture<Void> publishToSinkAndTrack(EventSink sink, VehicleEvent event) {
        try {
            return sink.publish(event).whenComplete((ignored, ex) -> {
                if (ex == null) return;
                // sink 异步失败只计数和打日志；await 调用方仍会收到异常并决定是否 ACK。
                failed.incrementAndGet();
                log.warn("sink {} publish failed", sink.name(), ex);
            });
        } catch (Throwable t) {
            failed.incrementAndGet();
            log.warn("sink {} publish failed", sink.name(), t);
            return CompletableFuture.failedFuture(t);
        }
    }

    private static WaitStrategy waitStrategy(String name) {
        return switch (name == null ? "yielding" : name.toLowerCase()) {
            case "blocking" -> new BlockingWaitStrategy();
            case "sleeping" -> new SleepingWaitStrategy();
            case "busy-spin" -> new BusySpinWaitStrategy();
            default -> new YieldingWaitStrategy();
        };
    }
}
