package com.lingniu.ingest.tdenginehistory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lingniu.ingest.sink.kafka.proto.LocationPayload;
import com.lingniu.ingest.sink.kafka.proto.RawFrameFactPayload;
import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope;

import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

public final class TdengineEnvelopeRows {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private TdengineEnvelopeRows() {
    }

    public static Optional<TdengineRawFrameRow> rawFrame(VehicleEnvelope envelope) {
        if (envelope == null || !envelope.hasRawFrameFact()) {
            return Optional.empty();
        }
        RawFrameFactPayload raw = envelope.getRawFrameFact();
        Map<String, String> metadata = new LinkedHashMap<>();
        metadata.putAll(envelope.getMetadataMap());
        metadata.putAll(raw.getMetadataMap());
        return Optional.of(new TdengineRawFrameRow(
                rawFrameTs(envelope, metadata),
                raw.getFrameId(),
                instant(envelope.getIngestTimeMs()),
                raw.getMessageId(),
                raw.getSubType(),
                instant(envelope.getEventTimeMs()),
                raw.getRawUri(),
                raw.getChecksum(),
                raw.getRawSizeBytes(),
                raw.getParseStatus().name().replace("PARSE_STATUS_", ""),
                raw.getParseError(),
                raw.getPeer(),
                json(metadata),
                parsedJson(envelope, metadata),
                protocol(envelope),
                vehicleKey(envelope, raw.getVehicleKey(), raw.getPhone()),
                firstNonBlank(raw.getVin(), envelope.getVin()),
                raw.getPhone()
        ));
    }

    public static Optional<TdengineLocationRow> location(VehicleEnvelope envelope) {
        if (envelope == null || !envelope.hasLocation()) {
            return Optional.empty();
        }
        LocationPayload location = envelope.getLocation();
        String rawUri = firstNonBlank(
                envelope.hasRawArchive() ? envelope.getRawArchive().getUri() : "",
                envelope.getMetadataOrDefault("rawArchiveUri", ""));
        String frameId = envelope.getMetadataOrDefault("frame_id", envelope.getEventId());
        return Optional.of(new TdengineLocationRow(
                instant(envelope.getEventTimeMs()),
                envelope.getEventId(),
                frameId,
                instant(envelope.getIngestTimeMs()),
                location.getLongitude(),
                location.getLatitude(),
                location.getAltitudeM(),
                location.getSpeedKmh(),
                location.getDirectionDeg(),
                location.getAlarmFlag(),
                location.getStatusFlag(),
                totalMileage(envelope),
                rawUri,
                protocol(envelope),
                vehicleKey(envelope, envelope.getMetadataOrDefault("vehicle_key", ""),
                        envelope.getMetadataOrDefault("phone", "")),
                envelope.getVin(),
                envelope.getMetadataOrDefault("phone", "")
        ));
    }

    private static Instant instant(long epochMillis) {
        return Instant.ofEpochMilli(epochMillis);
    }

    private static Instant rawFrameTs(VehicleEnvelope envelope, Map<String, String> metadata) {
        String rawArchiveEventId = firstNonBlank(metadata.get("rawArchiveEventId"), envelope.getEventId());
        if (rawArchiveEventId == null || rawArchiveEventId.isBlank()) {
            return instant(envelope.getEventTimeMs());
        }
        try {
            long sequenceId = Long.parseLong(rawArchiveEventId);
            long baseMillis = sequenceId / 1000L;
            long sameMillisecondOffset = sequenceId % 1000L;
            if (Math.abs(baseMillis - envelope.getEventTimeMs()) > 60_000L) {
                return instant(envelope.getEventTimeMs());
            }
            return Instant.ofEpochMilli(baseMillis + sameMillisecondOffset);
        } catch (NumberFormatException ignored) {
            return instant(envelope.getEventTimeMs());
        }
    }

    private static String protocol(VehicleEnvelope envelope) {
        return firstNonBlank(envelope.getSource(), "UNKNOWN");
    }

    private static String vehicleKey(VehicleEnvelope envelope, String explicitVehicleKey, String phone) {
        String key = firstKnownNonBlank(explicitVehicleKey, envelope.getMetadataOrDefault("vehicle_key", ""),
                envelope.getVin());
        if (!key.isBlank()) {
            return key;
        }
        if ("JT808".equalsIgnoreCase(protocol(envelope))) {
            String phoneValue = firstKnownNonBlank(phone, envelope.getMetadataOrDefault("phone", ""));
            if (!phoneValue.isBlank()) {
                return phoneValue.startsWith("jt808:") ? phoneValue : "jt808:" + phoneValue;
            }
        }
        return firstNonBlank(explicitVehicleKey, envelope.getMetadataOrDefault("vehicle_key", ""),
                envelope.getVin(), phone);
    }

    private static Double totalMileage(VehicleEnvelope envelope) {
        String value = envelope.getMetadataOrDefault("total_mileage_km", "");
        if (value.isBlank()) {
            return null;
        }
        return Double.parseDouble(value);
    }

    private static String parsedJson(VehicleEnvelope envelope, Map<String, String> metadata) {
        if (envelope.hasRawArchive() && !envelope.getRawArchive().getParsedJson().isBlank()) {
            return envelope.getRawArchive().getParsedJson();
        }
        return firstNonBlank(metadata.get("parsedJson"), metadata.get("parsed_json"));
    }

    private static String json(Map<String, String> metadata) {
        try {
            return OBJECT_MAPPER.writeValueAsString(metadata == null ? Map.of() : metadata);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("metadata cannot be serialized as json", e);
        }
    }

    private static String firstNonBlank(String... values) {
        for (String value : values) {
            if (value != null && !value.isBlank()) {
                return value;
            }
        }
        return "";
    }

    private static String firstKnownNonBlank(String... values) {
        for (String value : values) {
            if (value != null && !value.isBlank() && !"unknown".equalsIgnoreCase(value.trim())) {
                return value;
            }
        }
        return "";
    }
}
