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

import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.api.spi.DecodeException;
import com.lingniu.ingest.inbound.mqtt.model.MqttPayload;

import java.time.Instant;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;

public final class MqttProfileRegistry {

    private final Map<String, MqttProfile> profiles;

    public MqttProfileRegistry(Collection<MqttProfile> profiles) {
        Map<String, MqttProfile> indexed = new LinkedHashMap<>();
        for (MqttProfile profile : profiles) {
            String key = normalize(profile.name());
            if (key.isEmpty()) {
                throw new IllegalArgumentException("mqtt profile name must not be blank");
            }
            MqttProfile old = indexed.putIfAbsent(key, profile);
            if (old != null) {
                throw new IllegalArgumentException("duplicated mqtt profile: " + key);
            }
        }
        this.profiles = Map.copyOf(indexed);
    }

    public MqttPayload parse(String endpointName, String profileName, String topic, byte[] bytes) {
        MqttProfile profile;
        try {
            profile = resolve(profileName);
        } catch (DecodeException e) {
            return parseErrorPayload(endpointName, profileName, topic, bytes, e.getMessage(), true);
        }
        try {
            return profile.parse(endpointName, topic, bytes);
        } catch (DecodeException e) {
            return parseErrorPayload(endpointName, profile.name(), topic, bytes, e.getMessage(), false);
        }
    }

    public List<VehicleEvent> toEvents(MqttPayload payload) {
        if (payload != null && payload.data() != null
                && payload.data().path("_operationalError").asBoolean(false)) {
            return List.of(operationalErrorEvent(payload));
        }
        if (payload != null && payload.data() != null
                && payload.data().path("_unsupportedProfile").asBoolean(false)) {
            return List.of(parseErrorEvent(payload));
        }
        return resolve(payload.profile()).toEvents(payload);
    }

    public MqttProfile resolve(String profileName) {
        String key = normalize(profileName);
        MqttProfile profile = profiles.get(key);
        if (profile == null) {
            throw new DecodeException("unsupported mqtt profile: " + profileName);
        }
        return profile;
    }

    private static String normalize(String raw) {
        return raw == null ? "" : raw.trim().toLowerCase(Locale.ROOT);
    }

    private static MqttPayload parseErrorPayload(String endpointName,
                                                 String profileName,
                                                 String topic,
                                                 byte[] bytes,
                                                 String message,
                                                 boolean unsupportedProfile) {
        ObjectNode data = JsonNodeFactory.instance.objectNode();
        data.put("_parseError", true);
        data.put("_parseErrorMessage", message == null ? "" : message);
        data.put("_unsupportedProfile", unsupportedProfile);
        data.put("_rawBase64", Base64.getEncoder().encodeToString(bytes == null ? new byte[0] : bytes));
        return new MqttPayload(endpointName, normalize(profileName), topic, "unknown", Instant.now(), data);
    }

    public static MqttPayload operationalErrorPayload(String endpointName,
                                                      String profileName,
                                                      String topic,
                                                      byte[] bytes,
                                                      String phase,
                                                      String reason) {
        ObjectNode data = JsonNodeFactory.instance.objectNode();
        data.put("_operationalError", true);
        data.put("_phase", phase == null ? "" : phase);
        data.put("_reason", reason == null ? "" : reason);
        data.put("_rawBase64", Base64.getEncoder().encodeToString(bytes == null ? new byte[0] : bytes));
        return new MqttPayload(endpointName, normalize(profileName), topic, "unknown", Instant.now(), data);
    }

    private static VehicleEvent.Passthrough parseErrorEvent(MqttPayload payload) {
        Map<String, String> metadata = new HashMap<>();
        metadata.put("endpoint", payload.endpointName() == null ? "" : payload.endpointName());
        metadata.put("topic", payload.topic() == null ? "" : payload.topic());
        metadata.put("profile", payload.profile() == null ? "" : payload.profile());
        metadata.put("parseError", "true");
        metadata.put("unsupportedProfile", Boolean.toString(payload.data().path("_unsupportedProfile").asBoolean(false)));
        metadata.put("parseErrorMessage", payload.data().path("_parseErrorMessage").asText(""));
        addUnknownIdentity(metadata);
        return new VehicleEvent.Passthrough(
                UUID.randomUUID().toString(),
                "unknown",
                ProtocolId.MQTT_YUTONG,
                payload.deviceTime(),
                Instant.now(),
                null,
                Map.copyOf(metadata),
                0,
                rawBytes(payload.data().path("_rawBase64").asText("")));
    }

    private static VehicleEvent.Passthrough operationalErrorEvent(MqttPayload payload) {
        Map<String, String> metadata = new HashMap<>();
        metadata.put("endpoint", payload.endpointName() == null ? "" : payload.endpointName());
        metadata.put("topic", payload.topic() == null ? "" : payload.topic());
        metadata.put("profile", payload.profile() == null ? "" : payload.profile());
        metadata.put("operationalError", "true");
        metadata.put("phase", payload.data().path("_phase").asText(""));
        metadata.put("reason", payload.data().path("_reason").asText(""));
        addUnknownIdentity(metadata);
        return new VehicleEvent.Passthrough(
                UUID.randomUUID().toString(),
                "unknown",
                ProtocolId.MQTT_YUTONG,
                payload.deviceTime(),
                Instant.now(),
                null,
                Map.copyOf(metadata),
                0,
                rawBytes(payload.data().path("_rawBase64").asText("")));
    }

    private static void addUnknownIdentity(Map<String, String> metadata) {
        metadata.put("vin", "unknown");
        metadata.put("identityResolved", "false");
        metadata.put("identitySource", "UNKNOWN");
    }

    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];
        }
    }
}
