package com.lingniu.ingest.eventhistory;

import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader;
import com.lingniu.ingest.tdenginehistory.TdengineLocationQuery;
import com.lingniu.ingest.tdenginehistory.TdengineLocationRow;
import com.lingniu.ingest.tdenginehistory.TdenginePage;
import com.lingniu.ingest.tdenginehistory.TdenginePageCursor;
import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder;
import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery;
import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

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

class RawFrameHistoryControllerTest {

    @Test
    void queriesRawFramesByProtocolHexMessageIdAndCursor() throws Exception {
        CapturingReader reader = new CapturingReader(new TdenginePage<>(
                List.of(row("frame-1", "2026-06-29T12:00:01Z")),
                Optional.of(new TdenginePageCursor(Instant.parse("2026-06-29T12:00:01Z"), "frame-1"))));
        RawFrameHistoryController controller = new RawFrameHistoryController(reader);

        RawFrameHistoryController.RawFramePageResponse response = controller.rawFrames(
                "mqtt_yutong",
                null,
                "LB9A32A24P0LS1270",
                null,
                "0x0200",
                "not_parsed",
                "2026-06-29T00:00:00+08:00",
                "2026-06-29T23:59:59+08:00",
                TdengineQueryOrder.DESC,
                20,
                "2026-06-29T12:00:00Z",
                "frame-0");

        assertThat(reader.query.protocol()).isEqualTo("MQTT_YUTONG");
        assertThat(reader.query.vin()).isEqualTo("LB9A32A24P0LS1270");
        assertThat(reader.query.messageId()).isEqualTo(0x0200);
        assertThat(reader.query.parseStatus()).isEqualTo("NOT_PARSED");
        assertThat(reader.query.cursor()).isEqualTo(new TdenginePageCursor(
                Instant.parse("2026-06-29T12:00:00Z"), "frame-0"));
        assertThat(response.items()).hasSize(1);
        RawFrameHistoryController.RawFrameResponse item = response.items().getFirst();
        assertThat(item.messageId()).isEqualTo(0x0200);
        assertThat(item.messageIdHex()).isEqualTo("0x0200");
        assertThat(item.rawUri()).contains("MQTT_YUTONG");
        assertThat(item.metadataJson()).contains("rawArchiveUri");
        assertThat(item.metadata())
                .containsEntry("rawArchiveUri", item.rawUri())
                .containsEntry("plateNo", "粤BD12345")
                .containsEntry("rawJson", "{\"carName\":\"粤BD12345\"}");
        assertThat(item.parsedFields())
                .containsEntry("raw.rawArchiveUri", item.rawUri())
                .containsEntry("raw.plateNo", "粤BD12345");
        assertThat(reader.locationRawUri).isNull();
        assertThat(response.nextCursor()).isEqualTo(new RawFrameHistoryController.CursorResponse(
                "2026-06-29T12:00:01Z", "frame-1"));
    }

    @Test
    void returnsParsedJsonWithoutQueryingDerivedTelemetryTables() throws Exception {
        Instant ts = Instant.parse("2026-06-29T12:00:01Z");
        TdengineRawFrameRow raw = new TdengineRawFrameRow(
                ts,
                "frame-1",
                ts.plusMillis(500),
                0x0200,
                0,
                ts,
                "archive://2026/06/29/JT808/VIN-JT808-1/frame-1.bin",
                "sha256:frame-1",
                72,
                "SUCCEEDED",
                "",
                "115.231.168.135:43625",
                "{\"rawArchiveUri\":\"archive://2026/06/29/JT808/VIN-JT808-1/frame-1.bin\"}",
                "{\"location\":{\"longitude\":121.312516,\"latitude\":30.832808,\"speedKmh\":25.0},"
                        + "\"extras\":{\"gpsMileageKm\":14504.446}}",
                "JT808",
                "VIN-JT808-1",
                "VIN-JT808-1",
                "g7gps");
        CapturingReader reader = new CapturingReader(new TdenginePage<>(List.of(raw), Optional.empty()));
        reader.locationsByRawUri.add(new TdengineLocationRow(
                ts,
                "loc-1",
                "frame-1",
                ts.plusMillis(500),
                121.312516,
                30.832808,
                6.0,
                25.0,
                333.0,
                0,
                524290,
                14504.446,
                raw.rawUri(),
                "JT808",
                "VIN-JT808-1",
                "VIN-JT808-1",
                "g7gps"));
        RawFrameHistoryController controller = new RawFrameHistoryController(reader);

        RawFrameHistoryController.RawFramePageResponse response = controller.rawFrames(
                "jt808",
                null,
                "VIN-JT808-1",
                null,
                "0x0200",
                null,
                "2026-06-29T00:00:00+08:00",
                "2026-06-29T23:59:59+08:00",
                TdengineQueryOrder.DESC,
                20,
                null,
                null);

        RawFrameHistoryController.RawFrameResponse item = response.items().getFirst();
        assertThat(reader.locationRawUri).isNull();
        assertThat(item.parsedFields())
                .containsKey("location")
                .containsKey("extras");
    }

    @Test
    void preservesNullValuesFromParsedJsonWithoutFailingResponseMapping() throws Exception {
        Instant ts = Instant.parse("2026-06-29T12:00:01Z");
        TdengineRawFrameRow raw = new TdengineRawFrameRow(
                ts,
                "frame-with-null-json",
                ts.plusMillis(500),
                0x0002,
                0,
                ts,
                "archive://2026/06/29/GB32960/LB9A32A24P0LS1270/frame-with-null-json.bin",
                "sha256:null-json",
                128,
                "NOT_PARSED",
                "",
                "8.134.95.166:49706",
                "{\"rawArchiveUri\":\"archive://2026/06/29/GB32960/LB9A32A24P0LS1270/frame-with-null-json.bin\"}",
                "{\"header\":{\"command\":\"REALTIME_REPORT\"},\"commandBody\":null,\"signature\":null}",
                "GB32960",
                "LB9A32A24P0LS1270",
                "LB9A32A24P0LS1270",
                "");
        RawFrameHistoryController controller = new RawFrameHistoryController(
                new CapturingReader(new TdenginePage<>(List.of(raw), Optional.empty())));

        RawFrameHistoryController.RawFramePageResponse response = controller.rawFrames(
                "gb32960",
                null,
                "LB9A32A24P0LS1270",
                null,
                null,
                "not_parsed",
                "2026-06-29T00:00:00+08:00",
                "2026-06-29T23:59:59+08:00",
                TdengineQueryOrder.DESC,
                20,
                null,
                null);

        Map<String, Object> parsedFields = response.items().getFirst().parsedFields();
        assertThat(parsedFields).containsKeys("header", "commandBody", "signature");
        assertThat(parsedFields.get("commandBody")).isNull();
        assertThat(parsedFields.get("signature")).isNull();
    }

    @Test
    void rejectsRawFrameQueryWithoutAnyNarrowingFilter() {
        RawFrameHistoryController controller = new RawFrameHistoryController(
                new CapturingReader(new TdenginePage<>(List.of(), Optional.empty())));

        assertThatThrownBy(() -> controller.rawFrames(
                "JT808",
                null,
                null,
                null,
                null,
                null,
                "2026-06-29",
                "2026-06-30",
                TdengineQueryOrder.DESC,
                20,
                null,
                null))
                .isInstanceOfSatisfying(ResponseStatusException.class, ex ->
                        assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST));
    }

    @Test
    void rejectsLegacyXindaProtocolOnDefaultRawFrameApi() {
        RawFrameHistoryController controller = new RawFrameHistoryController(
                new CapturingReader(new TdenginePage<>(List.of(), Optional.empty())));

        assertThatThrownBy(() -> controller.rawFrames(
                "XINDA_PUSH",
                null,
                "LB9A32A24P0LS1270",
                null,
                null,
                null,
                "2026-06-29",
                "2026-06-30",
                TdengineQueryOrder.DESC,
                20,
                null,
                null))
                .isInstanceOfSatisfying(ResponseStatusException.class, ex ->
                        assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST));
    }

    private static TdengineRawFrameRow row(String frameId, String ts) {
        Instant instant = Instant.parse(ts);
        return new TdengineRawFrameRow(
                instant,
                frameId,
                instant.plusMillis(500),
                0x0200,
                0,
                instant,
                "archive://2026/06/29/MQTT_YUTONG/VEHICLE-MQTT-1/" + frameId + ".bin",
                "sha256:" + frameId,
                72,
                "NOT_PARSED",
                "",
                "mqtt-yutong",
                "{\"rawArchiveUri\":\"archive://2026/06/29/MQTT_YUTONG/VEHICLE-MQTT-1/" + frameId
                        + ".bin\",\"plateNo\":\"粤BD12345\",\"rawJson\":\"{\\\"carName\\\":\\\"粤BD12345\\\"}\"}",
                "",
                "MQTT_YUTONG",
                "LB9A32A24P0LS1270",
                "LB9A32A24P0LS1270",
                "");
    }

    private static final class CapturingReader implements TdengineHistoryReader {
        private final TdenginePage<TdengineRawFrameRow> response;
        private final List<TdengineLocationRow> locationsByRawUri = new ArrayList<>();
        private TdengineRawFrameQuery query;
        private String locationRawUri;

        private CapturingReader(TdenginePage<TdengineRawFrameRow> response) {
            this.response = response;
        }

        @Override
        public TdenginePage<TdengineRawFrameRow> queryRawFrames(TdengineRawFrameQuery query) throws IOException {
            this.query = query;
            return response;
        }

        @Override
        public TdenginePage<TdengineLocationRow> queryLocations(TdengineLocationQuery query) {
            throw new UnsupportedOperationException();
        }

        @Override
        public List<TdengineLocationRow> queryLocationsByRawUri(String protocol, String rawUri, int limit) {
            locationRawUri = rawUri;
            return locationsByRawUri;
        }
    }
}
