package com.lingniu.ingest.inbound.mqtt;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.identity.InMemoryVehicleIdentityService;
import com.lingniu.ingest.identity.VehicleIdentityBinding;
import com.lingniu.ingest.inbound.mqtt.mapper.YutongEventMapper;
import com.lingniu.ingest.inbound.mqtt.model.MqttPayload;
import com.lingniu.ingest.inbound.mqtt.parser.MqttPayloadParser;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class YutongEventMapperTest {

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final MqttPayloadParser parser = new MqttPayloadParser(objectMapper);
    private final YutongEventMapper mapper = new YutongEventMapper(new InMemoryVehicleIdentityService());

    @Test
    void rejectsMissingIdentityResolver() {
        assertThatThrownBy(() -> new YutongEventMapper(null))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("identityResolver");
    }

    @Test
    void parsesYutongSamplePayload() {
        String json = """
                {
                  "id": "abc",
                  "device": "LTEST000000000001",
                  "code": "C1",
                  "time": "20260413100000",
                  "data": {
                    "METER_SPEED": 52.3,
                    "TOTAL_MILEAGE": 123456.7,
                    "BATTERY_CAPACITY_SOC": 70,
                    "LONGITUDE": 116397128,
                    "LATITUDE": 39916527,
                    "GPSDirection": 90,
                    "LEFT_HYDROGEN": 5.5,
                    "VOLTAGE_OF_FC": 600.0,
                    "CURRENT_OF_FC": 120.0,
                    "TEMPT_OF_FC": 65.0,
                    "ON_GEAR": 3,
                    "ACC_PEDAL_APT": 25,
                    "BRAKEPEDALAPT": 0
                  }
                }
                """;

        MqttPayload payload = parser.parse("yutong", "yutong", "/ytforward/shln/dev1", json.getBytes());
        assertThat(payload.vin()).isEqualTo("LTEST000000000001");

        List<VehicleEvent> events = mapper.toEvents(payload);
        assertThat(events).hasSize(2);

        VehicleEvent.Realtime rt = (VehicleEvent.Realtime) events.stream()
                .filter(e -> e instanceof VehicleEvent.Realtime).findFirst().orElseThrow();
        assertThat(rt.source()).isEqualTo(ProtocolId.MQTT_YUTONG);
        assertThat(rt.vin()).isEqualTo("LTEST000000000001");
        assertThat(rt.payload().speedKmh()).isEqualTo(52.3);
        assertThat(rt.payload().batterySoc()).isEqualTo(70.0);
        assertThat(rt.payload().longitude()).isEqualTo(116.397128, org.assertj.core.data.Offset.offset(0.000001));
        assertThat(rt.payload().latitude()).isEqualTo(39.916527, org.assertj.core.data.Offset.offset(0.000001));
        assertThat(rt.payload().hydrogenRemainingKg()).isEqualTo(5.5);

        VehicleEvent.Location loc = (VehicleEvent.Location) events.stream()
                .filter(e -> e instanceof VehicleEvent.Location).findFirst().orElseThrow();
        assertThat(loc.payload().longitude()).isEqualTo(116.397128, org.assertj.core.data.Offset.offset(0.000001));
        assertThat(loc.metadata()).containsEntry("total_mileage_km", "123456.7");
    }

    @Test
    void resolvesVinThroughSharedIdentityServiceWhenDeviceIsAnExternalId() throws Exception {
        InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService();
        identity.bind(new VehicleIdentityBinding(
                ProtocolId.MQTT_YUTONG,
                "VIN-INTERNAL-001",
                "",
                "mqtt-device-001",
                ""));
        YutongEventMapper mapperWithIdentity = new YutongEventMapper(identity);

        MqttPayload payload = new MqttPayload(
                "yutong",
                "yutong",
                "/ytforward/shln/mqtt-device-001",
                "mqtt-device-001",
                java.time.Instant.parse("2026-06-22T08:00:00Z"),
                objectMapper.readTree("""
                        {
                          "METER_SPEED": 18.5,
                          "TOTAL_MILEAGE": 99.1
                        }
                        """));

        List<VehicleEvent> events = mapperWithIdentity.toEvents(payload);

        assertThat(events).singleElement().satisfies(event -> {
            assertThat(event.vin()).isEqualTo("VIN-INTERNAL-001");
            assertThat(event.metadata())
                    .containsEntry("identityResolved", "true")
                    .containsEntry("identitySource", "BOUND_DEVICE_ID")
                    .containsEntry("mqttDeviceId", "mqtt-device-001");
        });
    }

    @Test
    void resolvesVinThroughSharedIdentityServiceWhenPayloadUsesTerminalId() {
        InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService();
        identity.bind(new VehicleIdentityBinding(
                ProtocolId.MQTT_YUTONG,
                "VIN-INTERNAL-TID",
                "",
                "terminal-001",
                ""));
        YutongEventMapper mapperWithIdentity = new YutongEventMapper(identity);
        String json = """
                {
                  "terminalId": "terminal-001",
                  "time": "20260413100000",
                  "data": {
                    "METER_SPEED": 18.5,
                    "TOTAL_MILEAGE": 99.1
                  }
                }
                """;

        MqttPayload payload = parser.parse(
                "yutong", "yutong", "/ytforward/shln/terminal-001", json.getBytes());
        List<VehicleEvent> events = mapperWithIdentity.toEvents(payload);

        assertThat(events).singleElement().satisfies(event -> {
            assertThat(event.vin()).isEqualTo("VIN-INTERNAL-TID");
            assertThat(event.metadata())
                    .containsEntry("identityResolved", "true")
                    .containsEntry("identitySource", "BOUND_DEVICE_ID")
                    .containsEntry("mqttDeviceId", "terminal-001");
        });
    }

    @Test
    void prefersBoundDeviceIdentityOverVinLengthGuess() throws Exception {
        InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService();
        identity.bind(new VehicleIdentityBinding(
                ProtocolId.MQTT_YUTONG,
                "VIN-INTERNAL-017",
                "",
                "DEVICEID000000000",
                ""));
        YutongEventMapper mapperWithIdentity = new YutongEventMapper(identity);

        MqttPayload payload = new MqttPayload(
                "yutong",
                "yutong",
                "/ytforward/shln/DEVICEID000000000",
                "DEVICEID000000000",
                java.time.Instant.parse("2026-06-22T08:00:00Z"),
                objectMapper.readTree("{\"METER_SPEED\":18.5}"));

        List<VehicleEvent> events = mapperWithIdentity.toEvents(payload);

        assertThat(events).singleElement().satisfies(event -> {
            assertThat(event.vin()).isEqualTo("VIN-INTERNAL-017");
            assertThat(event.metadata()).containsEntry("identitySource", "BOUND_DEVICE_ID");
        });
    }

    @Test
    void identityResolverFailureStillProducesRealtimeEventWithUnknownVin() throws Exception {
        YutongEventMapper mapperWithFailingIdentity = new YutongEventMapper(lookup -> {
            throw new IllegalStateException("identity backend unavailable");
        });
        MqttPayload payload = new MqttPayload(
                "yutong",
                "yutong",
                "/ytforward/shln/mqtt-device-001",
                "mqtt-device-001",
                java.time.Instant.parse("2026-06-22T08:00:00Z"),
                objectMapper.readTree("""
                        {
                          "METER_SPEED": 18.5,
                          "TOTAL_MILEAGE": 99.1
                        }
                        """));

        List<VehicleEvent> events = mapperWithFailingIdentity.toEvents(payload);

        assertThat(events).singleElement().satisfies(event -> {
            assertThat(event).isInstanceOf(VehicleEvent.Realtime.class);
            assertThat(event.vin()).isEqualTo("unknown");
            assertThat(event.metadata())
                    .containsEntry("vin", "unknown")
                    .containsEntry("mqttDeviceId", "mqtt-device-001")
                    .containsEntry("identityResolved", "false")
                    .containsEntry("identitySource", "UNKNOWN")
                    .containsEntry("identityError", "true")
                    .containsEntry("identityErrorMessage", "identity backend unavailable");
        });
    }

    @Test
    void parseErrorPayloadKeepsDiagnosticMessageOnPassthroughEvent() throws Exception {
        MqttPayload payload = new MqttPayload(
                "endpoint-a",
                "yutong",
                "/ytforward/shln/bad-device",
                "unknown",
                java.time.Instant.parse("2026-06-22T08:00:00Z"),
                objectMapper.readTree("""
                        {
                          "_parseError": true,
                          "_parseErrorMessage": "Unexpected character at byte 1",
                          "_rawBase64": "e2JhZC1qc29u"
                        }
                        """));

        List<VehicleEvent> events = mapper.toEvents(payload);

        assertThat(events).singleElement().satisfies(event -> {
            assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class);
            VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event;
            assertThat(passthrough.data()).containsExactly("{bad-json".getBytes(java.nio.charset.StandardCharsets.UTF_8));
            assertThat(event.metadata())
                    .containsEntry("parseError", "true")
                    .containsEntry("parseErrorMessage", "Unexpected character at byte 1");
        });
    }
}
