package com.lingniu.ingest.vehiclestat.jt808;

import com.lingniu.ingest.sink.kafka.proto.TelemetryField;
import com.lingniu.ingest.sink.kafka.proto.TelemetrySnapshot;
import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope;
import com.lingniu.ingest.vehiclestat.DailyMileageStrategy;
import com.lingniu.ingest.vehiclestat.VehicleDailyStatResult;
import com.lingniu.ingest.vehiclestat.VehicleStatRepository;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Modifier;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;

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

class Jt808MileageStreamProcessorTest {

    @Test
    void recordsDailyMileageSamplesToVehicleStatRepository() {
        CapturingRepository repository = new CapturingRepository();
        Jt808MileageStreamProcessor processor = processor(repository);

        processor.process(envelope("2026-06-30T00:00:00Z", 120.0, 30.0, 100.0));
        processor.process(envelope("2026-06-30T00:01:00Z", 120.01, 30.0, 101.0));

        assertThat(repository.samples).containsExactly(100.0, 101.0);
        assertThat(repository.results).hasSize(2);
        VehicleDailyStatResult latest = repository.results.getLast();
        assertThat(latest.vin()).isEqualTo("VIN001");
        assertThat(latest.statDate()).isEqualTo(LocalDate.of(2026, 6, 30));
        assertThat(latest.dailyMileageKm()).hasValue(1.0);
        assertThat(latest.dailyMileageStrategy()).isEqualTo(DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF);
    }

    @Test
    void usesSimpleTotalMileageDifferenceInsteadOfAccumulatedPlausibilityDeltas() {
        CapturingRepository repository = new CapturingRepository();
        Jt808MileageStreamProcessor processor = processor(repository);

        processor.process(envelope("2026-06-30T00:00:00Z", 120.0, 30.0, 100.0));
        processor.process(envelope("2026-06-30T00:01:00Z", 120.0001, 30.0, 200.0));

        VehicleDailyStatResult latest = repository.results.getLast();
        assertThat(latest.dailyMileageKm()).hasValue(100.0);
    }

    @Test
    void skipsNonJt808Envelope() {
        CapturingRepository repository = new CapturingRepository();
        Jt808MileageStreamProcessor processor = processor(repository);
        VehicleEnvelope envelope = envelope("2026-06-30T00:00:00Z", 120.0, 30.0, 100.0)
                .toBuilder()
                .setSource("GB32960")
                .build();

        processor.process(envelope);

        assertThat(repository.results).isEmpty();
    }

    @Test
    void processDoesNotSerializeAllVehicleMileageUpdatesBehindOneMonitor() throws NoSuchMethodException {
        int modifiers = Jt808MileageStreamProcessor.class
                .getDeclaredMethod("process", VehicleEnvelope.class)
                .getModifiers();

        assertThat(Modifier.isSynchronized(modifiers)).isFalse();
    }

    private static Jt808MileageStreamProcessor processor(VehicleStatRepository repository) {
        return new Jt808MileageStreamProcessor(
                new Jt808LocationPointExtractor(),
                repository,
                ZoneId.of("Asia/Shanghai"));
    }

    private static VehicleEnvelope envelope(String eventTime, double longitude, double latitude, double totalMileageKm) {
        long eventTimeMs = Instant.parse(eventTime).toEpochMilli();
        return VehicleEnvelope.newBuilder()
                .setEventId("event-" + eventTimeMs)
                .setVin("VIN001")
                .setSource("JT808")
                .setEventTimeMs(eventTimeMs)
                .putMetadata("phone", "13900000001")
                .setTelemetrySnapshot(TelemetrySnapshot.newBuilder()
                        .setEventType("LOCATION")
                        .addFields(field("longitude", longitude))
                        .addFields(field("latitude", latitude))
                        .addFields(field("speed_kmh", 36.0))
                        .addFields(field("total_mileage_km", totalMileageKm)))
                .build();
    }

    private static TelemetryField field(String key, double value) {
        return TelemetryField.newBuilder()
                .setKey(key)
                .setValueType("DOUBLE")
                .setValue(Double.toString(value))
                .setQuality("GOOD")
                .build();
    }

    private static final class CapturingRepository implements VehicleStatRepository {
        private final List<VehicleDailyStatResult> results = new ArrayList<>();
        private final List<Double> samples = new ArrayList<>();
        private double firstSample = Double.NaN;

        @Override
        public Optional<VehicleDailyStatResult> recordDailyMileageSample(String vin, LocalDate statDate,
                                                                         double totalMileageKm) {
            samples.add(totalMileageKm);
            if (!Double.isFinite(firstSample)) {
                firstSample = totalMileageKm;
            }
            VehicleDailyStatResult result = new VehicleDailyStatResult(
                    vin,
                    statDate,
                    OptionalDouble.of(totalMileageKm - firstSample),
                    DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF);
            results.add(result);
            return Optional.of(result);
        }

        @Override
        public Optional<VehicleDailyStatResult> findDailyStat(String vin, LocalDate statDate) {
            return Optional.empty();
        }
    }
}
