package com.lingniu.ingest.eventhistory;

import com.lingniu.ingest.api.consumer.EnvelopeIngestResult;
import com.lingniu.ingest.api.consumer.EnvelopeIngestor;
import com.lingniu.ingest.sink.kafka.proto.RawArchiveRef;
import com.lingniu.ingest.sink.kafka.proto.RawFrameFactPayload;
import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope;
import com.lingniu.ingest.sink.kafka.proto.LocationPayload;
import com.lingniu.ingest.sink.kafka.proto.ParseStatusProto;
import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter;
import com.lingniu.ingest.tdenginehistory.TdengineLocationRow;
import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

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

class EventHistoryEnvelopeIngestorTest {

    @Test
    void rejectsInvalidEnvelopeBytes() {
        CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter();
        EventHistoryEnvelopeIngestor ingestor =
                new EventHistoryEnvelopeIngestor(tdengineWriter);

        assertThat(ingestor).isInstanceOf(EnvelopeIngestor.class);
        assertThatThrownBy(() -> ingestor.ingest(new byte[]{0x01, 0x02}))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("VehicleEnvelope");
    }

    @Test
    void tryIngestInvalidEnvelopeBytesReturnsInvalidWithoutWritingTdengine() {
        CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter();
        EventHistoryEnvelopeIngestor ingestor =
                new EventHistoryEnvelopeIngestor(tdengineWriter);

        var result = ingestor.tryIngest(new byte[]{0x01, 0x02});

        assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.INVALID_ENVELOPE);
        assertThat(result.message()).contains("VehicleEnvelope");
        assertThat(tdengineWriter.rawFrames).isEmpty();
        assertThat(tdengineWriter.locations).isEmpty();
    }

    @Test
    void tryIngestWritesRawAndLocationFactsToTdengineWhenWriterExists() throws Exception {
        CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter();
        EventHistoryEnvelopeIngestor ingestor = new EventHistoryEnvelopeIngestor(tdengineWriter);

        VehicleEnvelope envelope = VehicleEnvelope.newBuilder()
                .setSchemaVersion("1.0")
                .setEventId("jt808-location-1")
                .setVin("VINJT808001")
                .setSource("JT808")
                .setEventTimeMs(1_782_112_400_000L)
                .setIngestTimeMs(1_782_112_401_000L)
                .putMetadata("vehicle_key", "jt808:g7gps")
                .putMetadata("frame_id", "frame-jt808-1")
                .setRawArchive(RawArchiveRef.newBuilder()
                        .setUri("archive://jt808/2026/06/29/frame-jt808-1.bin")
                        .setSizeBytes(68))
                .setRawFrameFact(RawFrameFactPayload.newBuilder()
                        .setFrameId("frame-jt808-1")
                        .setVehicleKey("jt808:g7gps")
                        .setVin("VINJT808001")
                        .setPhone("013800000000")
                        .setMessageId(0x0200)
                        .setRawUri("archive://jt808/2026/06/29/frame-jt808-1.bin")
                        .setRawSizeBytes(68)
                        .setParseStatus(ParseStatusProto.PARSE_STATUS_SUCCEEDED))
                .setLocation(LocationPayload.newBuilder()
                        .setLongitude(113.12)
                        .setLatitude(23.45)
                        .setSpeedKmh(42.5))
                .build();

        EnvelopeIngestResult result = ingestor.tryIngest(envelope.toByteArray());

        assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.STORED);
        assertThat(tdengineWriter.rawFrames)
                .extracting(TdengineRawFrameRow::frameId)
                .containsExactly("frame-jt808-1");
        assertThat(tdengineWriter.locations)
                .extracting(TdengineLocationRow::factId)
                .containsExactly("jt808-location-1");
        assertThat(tdengineWriter.locations.getFirst().longitude()).isEqualTo(113.12);
    }

    @Test
    void tryIngestCanWriteTdengineFactsDirectly() {
        CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter();
        EventHistoryEnvelopeIngestor ingestor = new EventHistoryEnvelopeIngestor(tdengineWriter);

        EnvelopeIngestResult result = ingestor.tryIngest(
                jt808LocationEnvelope("jt808-location-1", "frame-jt808-1", "013800000001").toByteArray());

        assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.STORED);
        assertThat(tdengineWriter.rawFrames).extracting(TdengineRawFrameRow::frameId)
                .containsExactly("frame-jt808-1");
        assertThat(tdengineWriter.locations).extracting(TdengineLocationRow::factId)
                .containsExactly("jt808-location-1");
    }

    @Test
    void tryIngestAllBatchesFileStoreAndTdengineWrites() throws Exception {
        CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter();
        EventHistoryEnvelopeIngestor ingestor = new EventHistoryEnvelopeIngestor(tdengineWriter);

        VehicleEnvelope first = jt808LocationEnvelope("jt808-location-1", "frame-jt808-1", "013800000001");
        VehicleEnvelope second = jt808LocationEnvelope("jt808-location-2", "frame-jt808-2", "013800000002");

        List<EnvelopeIngestResult> results = ingestor.tryIngestAll(List.of(
                first.toByteArray(),
                new byte[]{0x01, 0x02},
                second.toByteArray()));

        assertThat(results).extracting(EnvelopeIngestResult::status)
                .containsExactly(
                        EnvelopeIngestResult.Status.STORED,
                        EnvelopeIngestResult.Status.INVALID_ENVELOPE,
                        EnvelopeIngestResult.Status.STORED);
        assertThat(tdengineWriter.rawFrames).extracting(TdengineRawFrameRow::frameId)
                .containsExactly("frame-jt808-1", "frame-jt808-2");
        assertThat(tdengineWriter.locations).extracting(TdengineLocationRow::factId)
                .containsExactly("jt808-location-1", "jt808-location-2");
    }

    private static VehicleEnvelope jt808LocationEnvelope(String eventId, String frameId, String phone) {
        return VehicleEnvelope.newBuilder()
                .setSchemaVersion("1.0")
                .setEventId(eventId)
                .setVin("VINJT808001")
                .setSource("JT808")
                .setEventTimeMs(1_782_112_400_000L)
                .setIngestTimeMs(1_782_112_401_000L)
                .putMetadata("vehicle_key", "jt808:" + phone)
                .putMetadata("frame_id", frameId)
                .setRawArchive(RawArchiveRef.newBuilder()
                        .setUri("archive://jt808/2026/06/29/" + frameId + ".bin")
                        .setSizeBytes(68))
                .setRawFrameFact(RawFrameFactPayload.newBuilder()
                        .setFrameId(frameId)
                        .setVehicleKey("jt808:" + phone)
                        .setVin("VINJT808001")
                        .setPhone(phone)
                        .setMessageId(0x0200)
                        .setRawUri("archive://jt808/2026/06/29/" + frameId + ".bin")
                        .setRawSizeBytes(68)
                        .setParseStatus(ParseStatusProto.PARSE_STATUS_SUCCEEDED))
                .setLocation(LocationPayload.newBuilder()
                        .setLongitude(113.12)
                        .setLatitude(23.45)
                        .setSpeedKmh(42.5))
                .build();
    }

    private static final class CapturingTdengineWriter implements TdengineHistoryWriter {
        private final List<TdengineRawFrameRow> rawFrames = new ArrayList<>();
        private final List<TdengineLocationRow> locations = new ArrayList<>();

        @Override
        public void appendRawFrames(List<TdengineRawFrameRow> rows) {
            rawFrames.addAll(rows);
        }

        @Override
        public void appendLocations(List<TdengineLocationRow> rows) {
            locations.addAll(rows);
        }

    }
}
