package com.lingniu.ingest.inbound.mqtt.mapper;

import com.fasterxml.jackson.databind.JsonNode;
import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.event.LocationPayload;
import com.lingniu.ingest.api.event.RealtimePayload;
import com.lingniu.ingest.api.event.TelemetryMetadataValues;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.api.spi.EventMapper;
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.inbound.mqtt.model.MqttPayload;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * 宇通 MQTT payload → 领域事件。
 *
 * <p>字段名取自旧项目的 {@code ReceptionDataVO}（大写 + 下划线），对字段映射一致性做出显式断言，
 * 便于与旧服务双跑对账。
 */
public final class YutongEventMapper implements EventMapper<MqttPayload> {

    private final VehicleIdentityResolver identityResolver;

    public YutongEventMapper(VehicleIdentityResolver identityResolver) {
        if (identityResolver == null) {
            throw new IllegalArgumentException("identityResolver must not be null");
        }
        this.identityResolver = identityResolver;
    }

    @Override
    public List<VehicleEvent> toEvents(MqttPayload payload) {
        JsonNode d = payload.data();
        if (d == null || !d.isObject()) return List.of();

        Instant ingestTime = Instant.now();
        String externalDeviceId = firstNonBlank(payload.deviceId(), payload.vin());
        IdentityResolution identity = resolveIdentity(payload);
        String vin = identity.vin();
        Map<String, String> meta = metadata(payload, externalDeviceId, vin, identity);

        if (d.path("_parseError").asBoolean(false)) {
            return List.of(new VehicleEvent.Passthrough(
                    UUID.randomUUID().toString(),
                    vin == null || vin.isBlank() ? "unknown" : vin,
                    ProtocolId.MQTT_YUTONG,
                    payload.deviceTime(),
                    ingestTime,
                    null,
                    withMeta(meta,
                            "parseError", "true",
                            "parseErrorMessage", d.path("_parseErrorMessage").asText("")),
                    0,
                    rawBytes(d.path("_rawBase64").asText(""))));
        }

        Double speed = optDouble(d, "METER_SPEED");
        Double mileage = optDouble(d, "TOTAL_MILEAGE");
        Double soc = optDouble(d, "BATTERY_CAPACITY_SOC");
        Double fcVoltage = optDouble(d, "VOLTAGE_OF_FC");
        Double fcCurrent = optDouble(d, "CURRENT_OF_FC");
        Double fcTemp = optDouble(d, "TEMPT_OF_FC");
        Double leftH2 = optDouble(d, "LEFT_HYDROGEN");
        Double lowH2Pressure = optDouble(d, "HYDROGEN_LOW_PRESSURE");
        Double longitude = optCoord(d, "LONGITUDE");
        Double latitude = optCoord(d, "LATITUDE");
        Double direction = optDouble(d, "GPSDirection");
        Double acc = optDouble(d, "ACC_PEDAL_APT");
        Double brake = optDouble(d, "BRAKEPEDALAPT");
        Integer gear = optInt(d, "ON_GEAR");

        RealtimePayload realtime = new RealtimePayload(
                speed, mileage, soc, null, null,
                fcVoltage, fcCurrent, fcTemp, leftH2, null, lowH2Pressure,
                null, null, null, gear, acc, brake,
                longitude, latitude, null, direction,
                null, null);

        List<VehicleEvent> out = new ArrayList<>(2);
        out.add(new VehicleEvent.Realtime(
                UUID.randomUUID().toString(),
                vin, ProtocolId.MQTT_YUTONG, payload.deviceTime(), ingestTime,
                null, meta, realtime));

        if (longitude != null && latitude != null) {
            LocationPayload loc = new LocationPayload(
                    longitude, latitude, 0.0,
                    speed == null ? 0.0 : speed,
                    direction == null ? 0.0 : direction,
                    0, 0);
            Map<String, String> locationMeta = mileage == null
                    ? meta
                    : withMeta(meta, "total_mileage_km", TelemetryMetadataValues.doubleValue(mileage));
            out.add(new VehicleEvent.Location(
                    UUID.randomUUID().toString(),
                    vin, ProtocolId.MQTT_YUTONG, payload.deviceTime(), ingestTime,
                    null, locationMeta, loc));
        }
        return out;
    }

    private IdentityResolution resolveIdentity(MqttPayload payload) {
        if (payload.hasIdentitySnapshot()) {
            VehicleIdentitySource source = VehicleIdentitySource.UNKNOWN;
            try {
                source = VehicleIdentitySource.valueOf(payload.identitySource());
            } catch (IllegalArgumentException ignored) {
                // 保守兜底，外部化的 payload 如果带了未知 source 不应影响接收主链路。
            }
            return new IdentityResolution(
                    new VehicleIdentity(payload.resolvedVin(), payload.identityResolved(), source),
                    payload.identityErrorMessage().isBlank() ? null : payload.identityErrorMessage());
        }
        String externalDeviceId = firstNonBlank(payload.deviceId(), payload.vin());
        String externalVin = payload.externalVin() == null ? "" : payload.externalVin();
        String phone = payload.phone() == null ? "" : payload.phone();
        String plateNo = payload.plateNo() == null ? "" : payload.plateNo();
        try {
            if (externalDeviceId == null
                    || externalDeviceId.isBlank()
                    || "unknown".equalsIgnoreCase(externalDeviceId.trim())) {
                if (!externalVin.isBlank() || !phone.isBlank() || !plateNo.isBlank()) {
                    return IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup(
                            ProtocolId.MQTT_YUTONG,
                            externalVin,
                            phone,
                            "",
                            plateNo)));
                }
                return IdentityResolution.ok(new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN));
            }
            VehicleIdentity bound = identityResolver.resolve(new VehicleIdentityLookup(
                    ProtocolId.MQTT_YUTONG,
                    externalVin,
                    phone,
                    externalDeviceId,
                    plateNo));
            if (bound.resolved() || !looksLikeVin(externalDeviceId)) {
                return IdentityResolution.ok(bound);
            }
            return IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup(
                        ProtocolId.MQTT_YUTONG,
                        externalDeviceId,
                        phone,
                        externalDeviceId,
                        plateNo)));
        } catch (RuntimeException e) {
            return new IdentityResolution(
                    new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN),
                    e.getMessage() == null ? "" : e.getMessage());
        }
    }

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

    private static boolean looksLikeVin(String value) {
        if (value == null || value.isBlank()) {
            return false;
        }
        String trimmed = value.trim();
        return trimmed.length() == 17;
    }

    private static Double optDouble(JsonNode n, String field) {
        JsonNode v = n.get(field);
        if (v == null || v.isNull() || !v.isNumber()) return null;
        return v.asDouble();
    }

    private static Integer optInt(JsonNode n, String field) {
        JsonNode v = n.get(field);
        if (v == null || v.isNull() || !v.isNumber()) return null;
        return v.asInt();
    }

    /** 宇通坐标约定：1e6 整数 或 已经是小数。同时过滤无效值 999999 / 0。 */
    private static Double optCoord(JsonNode n, String field) {
        Double v = optDouble(n, field);
        if (v == null) return null;
        if (v.intValue() == 999_999 || v == 0.0) return null;
        if (Math.abs(v) > 1000) return v / 1_000_000.0;
        return v;
    }

    private static Map<String, String> withMeta(Map<String, String> meta, String key, String value) {
        java.util.HashMap<String, String> copy = new java.util.HashMap<>(meta);
        copy.put(key, value == null ? "" : value);
        return Map.copyOf(copy);
    }

    private static Map<String, String> metadata(MqttPayload payload,
                                                String externalDeviceId,
                                                String vin,
                                                IdentityResolution identity) {
        java.util.HashMap<String, String> meta = new java.util.HashMap<>();
        meta.put("endpoint", payload.endpointName() == null ? "" : payload.endpointName());
        meta.put("topic", payload.topic() == null ? "" : payload.topic());
        meta.put("profile", payload.profile() == null ? "" : payload.profile());
        meta.put("externalVin", payload.externalVin() == null ? "" : payload.externalVin());
        meta.put("phone", payload.phone() == null ? "" : payload.phone());
        meta.put("mqttDeviceId", externalDeviceId == null ? "" : externalDeviceId);
        meta.put("plateNo", payload.plateNo() == null ? "" : payload.plateNo());
        meta.put("vin", vin == null || vin.isBlank() ? "unknown" : vin);
        meta.put("identityResolved", Boolean.toString(identity.identity().resolved()));
        meta.put("identitySource", identity.identity().source().name());
        if (identity.errorMessage() != null) {
            meta.put("identityError", "true");
            meta.put("identityErrorMessage", identity.errorMessage());
        }
        return Map.copyOf(meta);
    }

    private static Map<String, String> withMeta(Map<String, String> meta,
                                                String firstKey,
                                                String firstValue,
                                                String secondKey,
                                                String secondValue) {
        java.util.HashMap<String, String> copy = new java.util.HashMap<>(meta);
        copy.put(firstKey, firstValue == null ? "" : firstValue);
        copy.put(secondKey, secondValue == null ? "" : secondValue);
        return Map.copyOf(copy);
    }

    private static byte[] rawBytes(String base64) {
        if (base64 == null || base64.isBlank()) {
            return new byte[0];
        }
        try {
            return Base64.getDecoder().decode(base64);
        } catch (IllegalArgumentException e) {
            return new byte[0];
        }
    }

    private record IdentityResolution(VehicleIdentity identity, String errorMessage) {
        private static IdentityResolution ok(VehicleIdentity identity) {
            return new IdentityResolution(identity, null);
        }

        private String vin() {
            return identity.vin();
        }
    }
}
