package com.lingniu.ingest.inbound.mqtt;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.identity.InMemoryVehicleIdentityService;
import com.lingniu.ingest.inbound.mqtt.mapper.YutongEventMapper;
import com.lingniu.ingest.inbound.mqtt.parser.MqttPayloadParser;
import com.lingniu.ingest.inbound.mqtt.profile.MqttProfileRegistry;
import com.lingniu.ingest.inbound.mqtt.profile.YutongMqttProfile;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.util.List;

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

class MqttProfileRegistryTest {

    private final MqttProfileRegistry registry = new MqttProfileRegistry(List.of(
            new YutongMqttProfile(new MqttPayloadParser(new ObjectMapper()),
                    new YutongEventMapper(new InMemoryVehicleIdentityService()))));

    @Test
    void resolvesProfileCaseInsensitiveAndRoutesParseAndMapping() {
        byte[] payload = """
                {
                  "device": "LTEST000000000001",
                  "time": "20260413100000",
                  "data": {
                    "METER_SPEED": 52.3,
                    "TOTAL_MILEAGE": 123456.7,
                    "BATTERY_CAPACITY_SOC": 70,
                    "LONGITUDE": 116397128,
                    "LATITUDE": 39916527
                  }
                }
                """.getBytes(StandardCharsets.UTF_8);

        var parsed = registry.parse("endpoint-a", "YuTong", "/ytforward/shln/dev1", payload);

        assertThat(parsed.profile()).isEqualTo("yutong");
        assertThat(parsed.vin()).isEqualTo("LTEST000000000001");
        assertThat(registry.toEvents(parsed))
                .extracting(VehicleEvent::vin)
                .containsOnly("LTEST000000000001");
    }

    @Test
    void parserKeepsCommonExternalIdentityFieldsForColdStorageAndMapping() {
        byte[] payload = """
                {
                  "vin": "LVIN0000000000001",
                  "terminalId": "terminal-001",
                  "sim": "13800138000",
                  "plateNo": "粤B12345",
                  "time": "20260413100000",
                  "data": { "METER_SPEED": 12.3 }
                }
                """.getBytes(StandardCharsets.UTF_8);

        var parsed = registry.parse("endpoint-a", "yutong", "/ytforward/shln/terminal-001", payload);

        assertThat(parsed.externalVin()).isEqualTo("LVIN0000000000001");
        assertThat(parsed.deviceId()).isEqualTo("terminal-001");
        assertThat(parsed.phone()).isEqualTo("13800138000");
        assertThat(parsed.plateNo()).isEqualTo("粤B12345");
    }

    @Test
    void unsupportedProfileBecomesPassthroughEventForOperationalTraceability() {
        byte[] payload = "{\"device\":\"D1\"}".getBytes(StandardCharsets.UTF_8);

        var parsed = registry.parse("endpoint-a", "unknown", "/x", payload);

        assertThat(parsed.profile()).isEqualTo("unknown");
        assertThat(parsed.data().path("_parseError").asBoolean()).isTrue();
        assertThat(parsed.data().path("_unsupportedProfile").asBoolean()).isTrue();
        assertThat(registry.toEvents(parsed)).singleElement().satisfies(event -> {
            assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class);
            VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event;
            assertThat(passthrough.data()).containsExactly(payload);
            assertThat(passthrough.metadata())
                    .containsEntry("parseError", "true")
                    .containsEntry("unsupportedProfile", "true")
                    .containsEntry("profile", "unknown")
                    .containsEntry("vin", "unknown")
                    .containsEntry("identityResolved", "false")
                    .containsEntry("identitySource", "UNKNOWN");
        });
    }

    @Test
    void malformedPayloadBecomesPassthroughEventForOperationalTraceability() {
        byte[] payload = "{bad-json".getBytes(StandardCharsets.UTF_8);

        var parsed = registry.parse("endpoint-a", "yutong", "/ytforward/bad", payload);

        assertThat(parsed.vin()).isEqualTo("unknown");
        assertThat(parsed.data().path("_parseError").asBoolean()).isTrue();
        assertThat(registry.toEvents(parsed)).singleElement().satisfies(event -> {
            assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class);
            VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event;
            assertThat(passthrough.source()).isEqualTo(com.lingniu.ingest.api.ProtocolId.MQTT_YUTONG);
            assertThat(passthrough.data()).containsExactly(payload);
            assertThat(passthrough.metadata())
                    .containsEntry("parseError", "true")
                    .containsEntry("vin", "unknown")
                    .containsEntry("identityResolved", "false")
                    .containsEntry("identitySource", "UNKNOWN");
        });
    }

    @Test
    void operationalPayloadBecomesPassthroughEventWithFailureMetadata() {
        byte[] payload = """
                {"operationalError":true,"endpoint":"endpoint-a","phase":"init","reason":"bad uri"}
                """.getBytes(StandardCharsets.UTF_8);

        var parsed = MqttProfileRegistry.operationalErrorPayload(
                "endpoint-a", "yutong", "/operational", payload, "init", "bad uri");

        assertThat(registry.toEvents(parsed)).singleElement().satisfies(event -> {
            assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class);
            VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event;
            assertThat(passthrough.source()).isEqualTo(com.lingniu.ingest.api.ProtocolId.MQTT_YUTONG);
            assertThat(passthrough.vin()).isEqualTo("unknown");
            assertThat(passthrough.data()).containsExactly(payload);
            assertThat(passthrough.metadata())
                    .containsEntry("operationalError", "true")
                    .containsEntry("endpoint", "endpoint-a")
                    .containsEntry("profile", "yutong")
                    .containsEntry("phase", "init")
                    .containsEntry("reason", "bad uri")
                    .containsEntry("vin", "unknown")
                    .containsEntry("identityResolved", "false")
                    .containsEntry("identitySource", "UNKNOWN");
        });
    }
}
