package com.lingniu.ingest.protocol.gb32960.mapper;

import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.event.AlarmPayload;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960BodyParser;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960DecoderTest;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder;
import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParserRegistry;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
import com.lingniu.ingest.protocol.gb32960.model.EncryptType;
import com.lingniu.ingest.protocol.gb32960.model.Gb32960Header;
import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message;
import com.lingniu.ingest.protocol.gb32960.model.GeneralAlarmFlagBit;
import com.lingniu.ingest.protocol.gb32960.model.InfoBlock;
import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion;
import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag;
import org.junit.jupiter.api.Test;

import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.List;

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

class Gb32960EventMapperTest {

    @Test
    void realtimeReportProducesRealtimeAndLocationEvents() {
        byte[] frame = Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000002");

        Gb32960BodyParser body = new Gb32960BodyParser(new InfoBlockParserRegistry(
                List.of(new VehicleV2016BlockParser(), new PositionV2016BlockParser())));
        Gb32960Message msg = new Gb32960MessageDecoder(body).decode(ByteBuffer.wrap(frame));

        List<VehicleEvent> events = new Gb32960EventMapper().toEvents(msg);

        assertThat(events).hasSize(2);
        assertThat(events).anyMatch(e -> e instanceof VehicleEvent.Realtime);
        assertThat(events).anyMatch(e -> e instanceof VehicleEvent.Location);
        assertThat(events).allMatch(e -> e.source() == ProtocolId.GB32960);
        assertThat(events).allMatch(e -> e.vin().equals("LTEST000000000002"));
        VehicleEvent.Location location = events.stream()
                .filter(VehicleEvent.Location.class::isInstance)
                .map(VehicleEvent.Location.class::cast)
                .findFirst()
                .orElseThrow();
        assertThat(location.metadata()).containsEntry("total_mileage_km", "123456.7");
    }

    @Test
    void hydrogenLeakAlarmBitProducesCriticalSafetyAlarmEvent() {
        long hydrogenLeakFlag = 1L << GeneralAlarmFlagBit.HYDROGEN_LEAK.bitIndex();
        Gb32960Message msg = new Gb32960Message(
                new Gb32960Header(
                        ProtocolVersion.V2025,
                        CommandType.REALTIME_REPORT,
                        ResponseFlag.COMMAND,
                        "LTEST000000000003",
                        EncryptType.UNENCRYPTED,
                        0,
                        Instant.parse("2026-06-22T01:00:00Z")),
                List.of(new InfoBlock.Gb32960V2025.Alarm(
                        0,
                        hydrogenLeakFlag,
                        List.of(),
                        List.of(),
                        List.of(),
                        List.of(),
                        List.of())));

        List<VehicleEvent> events = new Gb32960EventMapper().toEvents(msg);

        VehicleEvent.Alarm alarm = events.stream()
                .filter(VehicleEvent.Alarm.class::isInstance)
                .map(VehicleEvent.Alarm.class::cast)
                .findFirst()
                .orElseThrow();
        assertThat(alarm.payload().level()).isEqualTo(AlarmPayload.AlarmLevel.CRITICAL);
        assertThat(alarm.payload().safetyCategory()).isEqualTo(AlarmPayload.SafetyCategory.HYDROGEN_LEAK);
        assertThat(alarm.payload().hydrogenLeakDetected()).isTrue();
        assertThat(alarm.payload().hydrogenLeakLevel()).isEqualTo(AlarmPayload.HydrogenLeakLevel.CRITICAL);
        assertThat(alarm.payload().hydrogenLeakActionRequired()).isTrue();
        assertThat(alarm.payload().activeBits()).contains("HYDROGEN_LEAK");
    }
}
