package com.lingniu.ingest.protocol.jt1078.media;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.identity.VehicleIdentity;
import com.lingniu.ingest.identity.VehicleIdentityLookup;
import com.lingniu.ingest.identity.VehicleIdentityResolver;
import com.lingniu.ingest.identity.VehicleIdentitySource;
import com.lingniu.ingest.sink.archive.ArchiveStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;

public final class Jt1078MediaArchiveService {

    private static final Logger log = LoggerFactory.getLogger(Jt1078MediaArchiveService.class);
    private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyy/MM/dd").withZone(ZoneId.systemDefault());

    private final ArchiveStore archive;
    private final VehicleIdentityResolver identityResolver;
    private final Consumer<VehicleEvent> eventPublisher;
    private final int segmentSeconds;
    private final Cache<String, String> publishedSegmentRefs = Caffeine.newBuilder()
            .expireAfterAccess(Duration.ofHours(2))
            .maximumSize(200_000)
            .build();

    public Jt1078MediaArchiveService(ArchiveStore archive,
                                     VehicleIdentityResolver identityResolver,
                                     Consumer<VehicleEvent> eventPublisher,
                                     int segmentSeconds) {
        this.archive = archive;
        this.identityResolver = identityResolver;
        this.eventPublisher = eventPublisher;
        this.segmentSeconds = Math.max(60, segmentSeconds);
    }

    public void archive(Jt1078RtpPacket packet, String peer) {
        if (packet == null || packet.payload() == null || packet.payload().length == 0) {
            return;
        }
        IdentityResolution identity = resolveIdentity(packet);
        String vin = identity.identity().vin();
        long segment = segment(packet.timestamp());
        String key = key(vin, packet, segment);
        try {
            // JT1078 是 RTP 分片流，同一分段追加写入 .media；只发布一次 MediaMeta 作为索引事件。
            String uri = archive.append(key, packet.payload());
            long segmentSizeBytes = archiveSize(key, packet.payload().length);
            publishOnce(packet, peer, identity, vin, segment, key, uri, segmentSizeBytes);
        } catch (Exception e) {
            log.warn("[jt1078] media archive failed sim={} channel={} sequence={} peer={}",
                    packet.sim(), packet.channelId(), packet.sequence(), peer, e);
            publishArchiveFailure(packet, peer, identity, vin, segment, key, e);
        }
    }

    private void publishArchiveFailure(Jt1078RtpPacket packet,
                                       String peer,
                                       IdentityResolution identity,
                                       String vin,
                                       long segment,
                                       String archiveKey,
                                       Exception error) {
        Instant now = Instant.now();
        Map<String, String> metadata = baseMetadata(packet, peer, identity);
        metadata.put("sequence", Integer.toString(packet.sequence()));
        metadata.put("segment", Long.toString(segment));
        metadata.put("archiveKey", archiveKey == null ? "" : archiveKey);
        metadata.put("archiveError", "true");
        metadata.put("archiveErrorMessage", error.getMessage() == null ? "" : error.getMessage());
        eventPublisher.accept(new VehicleEvent.Passthrough(
                UUID.randomUUID().toString(),
                vin,
                ProtocolId.JT1078,
                now,
                now,
                null,
                Map.copyOf(metadata),
                packet.dataType(),
                packet.payload()));
    }

    private void publishOnce(Jt1078RtpPacket packet,
                             String peer,
                             IdentityResolution identity,
                             String vin,
                             long segment,
                             String archiveKey,
                             String uri,
                             long segmentSizeBytes) {
        String segmentKey = archiveKey == null || archiveKey.isBlank()
                ? packet.sim() + ':' + packet.channelId() + ':' + packet.dataType() + ':' + segment
                : archiveKey;
        String old = publishedSegmentRefs.getIfPresent(segmentKey);
        if (old != null) {
            // 同一个分段只发一次索引事件，避免每个 RTP 包都在历史库产生一条 media meta。
            return;
        }
        publishedSegmentRefs.put(segmentKey, uri);
        Instant now = Instant.now();
        Map<String, String> metadata = baseMetadata(packet, peer, identity);
        metadata.put("segment", Long.toString(segment));
        metadata.put("sequence", Integer.toString(packet.sequence()));
        metadata.put("segmentSizeBytes", Long.toString(segmentSizeBytes));
        metadata.put("archiveKey", archiveKey == null ? "" : archiveKey);
        eventPublisher.accept(new VehicleEvent.MediaMeta(
                UUID.randomUUID().toString(),
                vin,
                ProtocolId.JT1078,
                now,
                now,
                null,
                Map.copyOf(metadata),
                packet.sim() + "-" + packet.channelId() + "-" + segment,
                mediaType(packet),
                segmentSizeBytes,
                uri));
    }

    private long archiveSize(String key, int fallbackBytes) {
        try {
            return archive.size(key);
        } catch (Exception e) {
            return Math.max(0, fallbackBytes);
        }
    }

    private IdentityResolution resolveIdentity(Jt1078RtpPacket packet) {
        try {
            return new IdentityResolution(identityResolver.resolve(new VehicleIdentityLookup(
                    ProtocolId.JT1078, "", packet.sim(), "", "")), null);
        } catch (RuntimeException e) {
            return new IdentityResolution(
                    new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN),
                    e.getMessage() == null ? "" : e.getMessage());
        }
    }

    private static Map<String, String> baseMetadata(Jt1078RtpPacket packet,
                                                    String peer,
                                                    IdentityResolution identity) {
        Map<String, String> metadata = new LinkedHashMap<>();
        metadata.put("sim", packet.sim());
        metadata.put("channelId", Integer.toString(packet.channelId()));
        metadata.put("dataType", Integer.toString(packet.dataType()));
        metadata.put("packetType", Integer.toString(packet.packetType()));
        metadata.put("peer", peer == null ? "" : peer);
        metadata.put("vin", identity.identity().vin());
        metadata.put("identityResolved", Boolean.toString(identity.identity().resolved()));
        metadata.put("identitySource", identity.identity().source().name());
        if (identity.errorMessage() != null) {
            metadata.put("identityError", "true");
            metadata.put("identityErrorMessage", identity.errorMessage());
        }
        return metadata;
    }

    private long segment(long timestamp) {
        long seconds = timestamp > 10_000_000_000L ? timestamp / 1000 : timestamp;
        // 按配置秒数对齐分段，保证同一路通道同一时间窗落到同一个 archive key。
        return seconds - Math.floorMod(seconds, segmentSeconds);
    }

    private static String key(String vin, Jt1078RtpPacket packet, long segment) {
        String day = DAY_FMT.format(Instant.ofEpochSecond(Math.max(0, segment)));
        String safeVin = safe(vin);
        return "jt1078/" + day + "/" + safeVin + "/ch-" + packet.channelId()
                + "/dt-" + packet.dataType() + "/" + segment + ".media";
    }

    private static String mediaType(Jt1078RtpPacket packet) {
        return "rtp,dataType=" + packet.dataType()
                + ",packetType=" + packet.packetType()
                + ",channel=" + packet.channelId();
    }

    private static String safe(String raw) {
        if (raw == null || raw.isBlank()) {
            return "unknown";
        }
        return raw.replaceAll("[^A-Za-z0-9_.-]", "_");
    }

    private record IdentityResolution(VehicleIdentity identity, String errorMessage) {
    }
}
