package com.lingniu.ingest.core.dispatcher;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.lingniu.ingest.api.event.RawArchiveKeys;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.api.pipeline.IngestContext;
import com.lingniu.ingest.api.pipeline.RawFrame;
import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor;
import com.lingniu.ingest.core.concurrency.DisruptorEventBus;
import com.lingniu.ingest.core.pipeline.InterceptorChain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 核心分发器：RawFrame → 拦截链 → Handler → 事件 → Disruptor EventBus。
 *
 * <p>所有 Inbound Adapter（Netty / MQTT / PushClient）都调用 {@link #dispatch(RawFrame)}，
 * 协议差异在这里被彻底抹平。
 *
 * <p>对于带 {@code @AsyncBatch} 的 Handler，Dispatcher 不直接反射调用，而是把消息交给
 * {@link AsyncBatchExecutor} 进行聚合 → 批量调用 → 异步发布事件。
 */
public final class Dispatcher {

    private static final Logger log = LoggerFactory.getLogger(Dispatcher.class);
    private static final AtomicLong RAW_ARCHIVE_SEQUENCE = new AtomicLong();
    private static final String REQUIRED_DURABLE_SINK = "kafka";
    private static final ObjectMapper RAW_ARCHIVE_JSON = JsonMapper.builder()
            .findAndAddModules()
            .build();

    private final HandlerRegistry registry;
    private final InterceptorChain interceptors;
    private final HandlerInvoker invoker;
    private final DisruptorEventBus eventBus;
    private final AsyncBatchExecutor batchExecutor;

    public Dispatcher(HandlerRegistry registry,
                      InterceptorChain interceptors,
                      HandlerInvoker invoker,
                      DisruptorEventBus eventBus,
                      AsyncBatchExecutor batchExecutor) {
        this.registry = registry;
        this.interceptors = interceptors;
        this.invoker = invoker;
        this.eventBus = eventBus;
        this.batchExecutor = batchExecutor;
    }

    public void dispatch(RawFrame frame) {
        IngestContext ctx = new IngestContext(UUID.randomUUID().toString());
        try {
            DispatchPlan plan = mapFrameToEvents(frame, ctx, false);
            for (VehicleEvent e : plan.events()) {
                eventBus.publish(e);
            }
            if (plan.failure() != null) {
                throw plan.failure();
            }
        } catch (Throwable t) {
            log.error("dispatch failure traceId={}", ctx.traceId(), t);
            interceptors.onError(t, ctx);
        }
    }

    public CompletableFuture<Void> dispatchAndAwait(RawFrame frame) {
        IngestContext ctx = new IngestContext(UUID.randomUUID().toString());
        try {
            DispatchPlan plan = mapFrameToEvents(frame, ctx, true);
            if (plan.events().isEmpty() && plan.failure() == null) {
                return CompletableFuture.failedFuture(new IllegalStateException(
                        "awaited dispatch produced no events; required sink '" + REQUIRED_DURABLE_SINK + "' was not reached"));
            }
            CompletableFuture<?>[] futures = plan.events().stream()
                    .map(event -> eventBus.publishAndAwait(event, REQUIRED_DURABLE_SINK))
                    .toArray(CompletableFuture[]::new);
            CompletableFuture<Void> boundary = CompletableFuture.allOf(futures);
            if (plan.failure() == null) {
                return boundary;
            }
            return boundary.handle((ignored, publishFailure) -> {
                log.error("dispatch failure traceId={}", ctx.traceId(), plan.failure());
                interceptors.onError(plan.failure(), ctx);
                if (publishFailure != null) {
                    throw new CompletionException(publishFailure);
                }
                throw new CompletionException(plan.failure());
            });
        } catch (Throwable t) {
            log.error("dispatch failure traceId={}", ctx.traceId(), t);
            interceptors.onError(t, ctx);
            return CompletableFuture.failedFuture(t);
        }
    }

    public CompletableFuture<Void> dispatchAndAwait(RawFrame frame, Duration timeout) {
        CompletableFuture<Void> future = dispatchAndAwait(frame);
        if (timeout == null || timeout.isZero() || timeout.isNegative()) {
            return future;
        }
        return future.orTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS);
    }

    private DispatchPlan mapFrameToEvents(RawFrame frame, IngestContext ctx, boolean awaitDurability) {
        List<VehicleEvent> out = new ArrayList<>();
        // 在 interceptor 之前发 RawArchive，保证原始字节被无条件落盘（dedup/rate-limit
        // 不会过滤它），满足"原始可回放"目标。archive 的写盘靠下游 Sink 消费。
        RawArchiveLookup rawArchive = appendRawArchive(frame, ctx, out);

        try {
            if (!interceptors.before(frame, ctx)) {
                log.debug("frame aborted: {}", ctx.abortReason());
                return new DispatchPlan(out, null);
            }

            List<HandlerDefinition> handlers = registry.resolve(
                    frame.protocolId(), frame.command(), frame.infoType());
            if (handlers.isEmpty()) {
                log.debug("no handler for {} cmd=0x{} info=0x{}",
                        frame.protocolId(), Integer.toHexString(frame.command()),
                        Integer.toHexString(frame.infoType()));
                return new DispatchPlan(out, null);
            }

            for (HandlerDefinition def : handlers) {
                if (def.asyncBatch() != null) {
                    if (awaitDurability) {
                        throw new AwaitedAsyncBatchUnsupportedException(
                                "@AsyncBatch handler cannot be used with dispatchAndAwait until batch completion is awaited: "
                                        + def.method());
                    }
                    batchExecutor.submit(def, frame.payload(), event -> enrichWithRawArchive(event, rawArchive));
                    continue;
                }
                List<VehicleEvent> events = invoker.invoke(def, frame, ctx);
                for (VehicleEvent e : events) {
                    e = enrichWithRawArchive(e, rawArchive);
                    interceptors.after(e, ctx);
                    out.add(e);
                }
            }
            return new DispatchPlan(out, null);
        } catch (Throwable t) {
            if (t instanceof AwaitedAsyncBatchUnsupportedException) {
                return new DispatchPlan(List.of(), t);
            }
            return new DispatchPlan(out, t);
        }
    }

    /**
     * 从 {@link RawFrame} 构造一条 {@link VehicleEvent.RawArchive} 发到 EventBus。
     * 仅在 {@code rawBytes} 非空时发——有些入站适配器（未来）可能只传解析后的对象。
     *
     * <p>VIN 取自 sourceMeta 里的 {@code vin} key（由各入站适配器负责填充）；缺失时
     * 留空字符串，archive sink 会用 "unknown-vin" 占位保证 key 可解析。
     */
    private RawArchiveLookup appendRawArchive(RawFrame frame, IngestContext ctx, List<VehicleEvent> out) {
        byte[] bytes = frame.rawBytes();
        if (bytes == null || bytes.length == 0) return RawArchiveLookup.empty();

        Map<String, String> meta = frame.sourceMeta() == null
                ? Map.of()
                : new LinkedHashMap<>(frame.sourceMeta());
        String parsedJson = parsedJson(frame.payload(), meta.remove(RawArchiveKeys.META_PARSED_JSON));
        String vin = meta.getOrDefault("vin", "");
        Instant ingestTime = frame.receivedAt() != null ? frame.receivedAt() : Instant.now();
        String eventId = nextRawArchiveEventId(ingestTime);
        String key = RawArchiveKeys.key(ingestTime, frame.protocolId(), vin, eventId);
        Map<String, String> archiveMeta = addRawArchiveMetadata(meta, eventId, key);

        VehicleEvent.RawArchive raw = new VehicleEvent.RawArchive(
                eventId,
                vin,
                frame.protocolId(),
                ingestTime,
                ingestTime,
                ctx.traceId(),
                archiveMeta,
                frame.command(),
                frame.infoType(),
                bytes,
                parsedJson);
        out.add(raw);
        return new RawArchiveLookup(eventId, key, RawArchiveKeys.logicalUri(key));
    }

    private static String parsedJson(Object payload, String explicitParsedJson) {
        if (explicitParsedJson != null && !explicitParsedJson.isBlank()) {
            return explicitParsedJson;
        }
        if (payload == null) {
            return "{}";
        }
        try {
            return RAW_ARCHIVE_JSON.writeValueAsString(payload);
        } catch (JsonProcessingException ex) {
            return "{\"serializationError\":\"" + escapeJson(ex.getMessage()) + "\",\"payloadType\":\""
                    + escapeJson(payload.getClass().getName()) + "\"}";
        }
    }

    private static String escapeJson(String value) {
        if (value == null) {
            return "";
        }
        return value.replace("\\", "\\\\").replace("\"", "\\\"");
    }

    private static String nextRawArchiveEventId(Instant ingestTime) {
        // 文件名需要可排序且单进程内唯一：毫秒时间放大到微秒量级，再用 AtomicLong 递增兜底。
        long base = (ingestTime == null ? Instant.now() : ingestTime).toEpochMilli() * 1000L;
        long next = RAW_ARCHIVE_SEQUENCE.updateAndGet(previous -> Math.max(previous + 1, base));
        return Long.toString(next);
    }

    private static Map<String, String> addRawArchiveMetadata(Map<String, String> metadata,
                                                             String eventId,
                                                             String key) {
        Map<String, String> out = new LinkedHashMap<>(metadata == null ? Map.of() : metadata);
        out.putIfAbsent(RawArchiveKeys.META_EVENT_ID, eventId);
        out.putIfAbsent(RawArchiveKeys.META_KEY, key);
        out.putIfAbsent(RawArchiveKeys.META_URI, RawArchiveKeys.logicalUri(key));
        return Map.copyOf(out);
    }

    private static VehicleEvent enrichWithRawArchive(VehicleEvent event, RawArchiveLookup rawArchive) {
        if (event == null || rawArchive.isEmpty() || event instanceof VehicleEvent.RawArchive) {
            return event;
        }
        // 派生事件携带 rawArchiveUri，便于后续服务从 Kafka 事件回查原始报文。
        Map<String, String> metadata = addRawArchiveMetadata(event.metadata(), rawArchive.eventId(), rawArchive.key());
        return switch (event) {
            case VehicleEvent.Realtime e -> new VehicleEvent.Realtime(
                    e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(),
                    e.traceId(), metadata, e.payload());
            case VehicleEvent.Location e -> new VehicleEvent.Location(
                    e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(),
                    e.traceId(), metadata, e.payload());
            case VehicleEvent.Alarm e -> new VehicleEvent.Alarm(
                    e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(),
                    e.traceId(), metadata, e.payload());
            case VehicleEvent.Login e -> new VehicleEvent.Login(
                    e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(),
                    e.traceId(), metadata, e.iccid(), e.protocolVersion());
            case VehicleEvent.Logout e -> new VehicleEvent.Logout(
                    e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(),
                    e.traceId(), metadata);
            case VehicleEvent.Heartbeat e -> new VehicleEvent.Heartbeat(
                    e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(),
                    e.traceId(), metadata);
            case VehicleEvent.MediaMeta e -> new VehicleEvent.MediaMeta(
                    e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(),
                    e.traceId(), metadata, e.mediaId(), e.mediaType(), e.sizeBytes(), e.archiveRef());
            case VehicleEvent.Passthrough e -> new VehicleEvent.Passthrough(
                    e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(),
                    e.traceId(), metadata, e.passthroughType(), e.data());
            case VehicleEvent.RawArchive e -> e;
        };
    }

    private record RawArchiveLookup(String eventId, String key, String uri) {
        private static RawArchiveLookup empty() {
            return new RawArchiveLookup("", "", "");
        }

        private boolean isEmpty() {
            return key == null || key.isBlank();
        }
    }

    private record DispatchPlan(List<VehicleEvent> events, Throwable failure) {
    }

    private static final class AwaitedAsyncBatchUnsupportedException extends IllegalStateException {
        private AwaitedAsyncBatchUnsupportedException(String message) {
            super(message);
        }
    }
}
