package com.lingniu.ingest.observability;

import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.api.pipeline.IngestContext;
import com.lingniu.ingest.api.pipeline.IngestInterceptor;
import com.lingniu.ingest.api.pipeline.RawFrame;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import org.springframework.core.Ordered;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 指标拦截器：自动以 {@link IngestInterceptor} 形式接入拦截链，无侵入采集核心流量指标。
 *
 * <p>暴露指标：
 * <ul>
 *   <li>{@code ingest_frames_total{protocol}} - 入站帧计数
 *   <li>{@code ingest_events_total{protocol,type}} - 事件产出计数
 *   <li>{@code ingest_errors_total{protocol}} - 异常计数
 *   <li>{@code ingest_frame_duration_seconds{protocol}} - 单帧处理耗时
 * </ul>
 */
public class IngestMetrics implements IngestInterceptor, Ordered {

    private static final String ATTR_TIMER_SAMPLE = "metrics.timer.sample";
    private static final String ATTR_PROTOCOL = "metrics.protocol";

    private final MeterRegistry registry;
    private final ConcurrentMap<ProtocolId, Counter> frameCounters = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, Counter> errorCounters = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, Timer> frameTimers = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, Counter> eventCounters = new ConcurrentHashMap<>();

    public IngestMetrics(MeterRegistry registry) {
        this.registry = registry;
    }

    @Override
    public boolean before(RawFrame frame, IngestContext ctx) {
        ProtocolId p = frame.protocolId() == null ? ProtocolId.UNKNOWN : frame.protocolId();
        ctx.attr(ATTR_PROTOCOL, p);
        frameCounters.computeIfAbsent(p, k ->
                Counter.builder("ingest_frames_total").tag("protocol", k.name()).register(registry)).increment();
        Timer.Sample sample = Timer.start(registry);
        ctx.attr(ATTR_TIMER_SAMPLE, sample);
        return true;
    }

    @Override
    public void after(VehicleEvent event, IngestContext ctx) {
        String key = event.source().name() + ":" + event.getClass().getSimpleName();
        eventCounters.computeIfAbsent(key, k -> {
            String[] parts = k.split(":");
            return Counter.builder("ingest_events_total")
                    .tags(Tags.of("protocol", parts[0], "type", parts[1]))
                    .register(registry);
        }).increment();

        Timer.Sample s = ctx.attr(ATTR_TIMER_SAMPLE);
        if (s != null) {
            stopTimer(ctx, protocolTag(ctx));
        }
    }

    @Override
    public void onError(Throwable error, IngestContext ctx) {
        String protocol = protocolTag(ctx);
        errorCounters.computeIfAbsent(protocol, p ->
                Counter.builder("ingest_errors_total").tag("protocol", p).register(registry)).increment();
        stopTimer(ctx, protocol);
    }

    @Override
    public int getOrder() {
        return 10; // 尽早介入，晚于 Tracing（order=0）
    }

    private void stopTimer(IngestContext ctx, String protocol) {
        if (ctx == null) {
            return;
        }
        Timer.Sample sample = ctx.attr(ATTR_TIMER_SAMPLE);
        if (sample == null) {
            return;
        }
        Timer timer = frameTimers.computeIfAbsent(protocol, p ->
                Timer.builder("ingest_frame_duration_seconds").tag("protocol", p).register(registry));
        sample.stop(timer);
        ctx.attr(ATTR_TIMER_SAMPLE, null);
    }

    private static String protocolTag(IngestContext ctx) {
        if (ctx == null) {
            return "unknown";
        }
        ProtocolId protocol = ctx.attr(ATTR_PROTOCOL);
        return protocol == null || protocol == ProtocolId.UNKNOWN ? "unknown" : protocol.name();
    }
}
