package com.lingniu.ingest.vehiclestat;

import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import java.time.LocalDate;

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

class JdbcVehicleStatMetricRepositoryTest {

    @Test
    void recordsDailyMileageIntoCommonMetricTable() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource(
                "jdbc:h2:mem:vehicle_stat_metric;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1",
                "sa",
                ""));
        JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate);

        repository.recordDailyMileageSample("VIN001", LocalDate.of(2026, 6, 30), 100.0);
        repository.recordDailyMileageSample("VIN001", LocalDate.of(2026, 6, 30), 115.5);

        assertThat(repository.findDailyStat("VIN001", LocalDate.of(2026, 6, 30)))
                .isPresent()
                .get()
                .extracting(result -> result.dailyMileageKm().orElseThrow())
                .isEqualTo(15.5);
        assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM vehicle_stat_metric", Integer.class))
                .isOne();
        assertThat(jdbcTemplate.queryForObject(
                "SELECT metric_key FROM vehicle_stat_metric WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'",
                String.class)).isEqualTo("daily_mileage_km");
        assertThat(jdbcTemplate.queryForObject(
                "SELECT calculation_method FROM vehicle_stat_metric WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'",
                String.class)).isEqualTo("JT808_TOTAL_MILEAGE_DIFF");
        assertThat(jdbcTemplate.queryForObject("""
                SELECT first_total_mileage_km
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(100.0);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT latest_total_mileage_km
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(115.5);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT COUNT(*)
                FROM information_schema.tables
                WHERE table_name = 'vehicle_daily_stat'
                """, Integer.class)).isZero();
    }

    @Test
    void recordsJt808GpsMileageDifferenceInsideMetricTable() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource(
                "jdbc:h2:mem:vehicle_stat_metric_jt808_diff;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1",
                "sa",
                ""));
        JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate);

        VehicleDailyStatResult first = repository.recordDailyMileageSample(
                "VIN001", LocalDate.of(2026, 7, 1), 1000.5).orElseThrow();
        VehicleDailyStatResult second = repository.recordDailyMileageSample(
                "VIN001", LocalDate.of(2026, 7, 1), 1008.75).orElseThrow();

        assertThat(first.dailyMileageKm()).hasValue(0.0);
        assertThat(second.dailyMileageKm()).hasValue(8.25);
        assertThat(jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM vehicle_stat_metric WHERE vin = 'VIN001' AND stat_date = DATE '2026-07-01'",
                Integer.class)).isOne();
        assertThat(jdbcTemplate.queryForObject("""
                SELECT metric_value
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(8.25);
        assertThat(jdbcTemplate.queryForList(
                "SELECT table_name FROM information_schema.tables WHERE table_name = 'jt808_daily_mileage'",
                String.class)).isEmpty();
    }

    @Test
    void recalculatesJt808MileageFromSingleMetricRowAfterRestart() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource(
                "jdbc:h2:mem:vehicle_stat_metric_jt808_out_of_order;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1",
                "sa",
                ""));
        JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate);

        repository.recordDailyMileageSample("VIN001", LocalDate.of(2026, 7, 1), 1000.5).orElseThrow();
        JdbcVehicleStatMetricRepository restartedRepository = new JdbcVehicleStatMetricRepository(jdbcTemplate);
        VehicleDailyStatResult higherSample = restartedRepository.recordDailyMileageSample(
                "VIN001", LocalDate.of(2026, 7, 1), 1010.0).orElseThrow();

        assertThat(higherSample.dailyMileageKm()).hasValue(9.5);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT COUNT(*)
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND stat_date = DATE '2026-07-01'
                """, Integer.class)).isOne();
        assertThat(jdbcTemplate.queryForObject("""
                SELECT first_total_mileage_km
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(1000.5);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT latest_total_mileage_km
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(1010.0);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT metric_value
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(9.5);
    }

    @Test
    void recalculatesJt808MileageWhenEarlierLowerMileageArrivesAfterRestart() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource(
                "jdbc:h2:mem:vehicle_stat_metric_jt808_replay_lower;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1",
                "sa",
                ""));
        JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate);

        repository.recordDailyMileageSample("VIN001", LocalDate.of(2026, 7, 1), 1010.0).orElseThrow();
        JdbcVehicleStatMetricRepository restartedRepository = new JdbcVehicleStatMetricRepository(jdbcTemplate);
        VehicleDailyStatResult replayedEarlierSample = restartedRepository.recordDailyMileageSample(
                "VIN001", LocalDate.of(2026, 7, 1), 1000.5).orElseThrow();

        assertThat(replayedEarlierSample.dailyMileageKm()).hasValue(9.5);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT first_total_mileage_km
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(1000.5);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT latest_total_mileage_km
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(1010.0);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT COUNT(*)
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND stat_date = DATE '2026-07-01'
                """, Integer.class)).isOne();
    }

    @Test
    void ignoresLegacySplitMileageStateMetricsWhenRecordingNewGpsTotalMileage() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource(
                "jdbc:h2:mem:vehicle_stat_metric_ignore_legacy_state;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1",
                "sa",
                ""));
        JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate);
        jdbcTemplate.update("""
                INSERT INTO vehicle_stat_metric
                (vin, stat_date, metric_key, metric_value, metric_unit, calculation_method)
                VALUES
                ('VIN001', DATE '2026-07-01', 'daily_mileage_start_total_km', 100.0, 'km', 'JT808_TOTAL_MILEAGE_DIFF'),
                ('VIN001', DATE '2026-07-01', 'daily_mileage_latest_total_km', 105.0, 'km', 'JT808_TOTAL_MILEAGE_DIFF')
                """);

        VehicleDailyStatResult result = repository.recordDailyMileageSample(
                "VIN001", LocalDate.of(2026, 7, 1), 110.0).orElseThrow();

        assertThat(result.dailyMileageKm()).hasValue(0.0);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT first_total_mileage_km
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(110.0);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT latest_total_mileage_km
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'
                """, Double.class)).isEqualTo(110.0);
        assertThat(jdbcTemplate.queryForObject("""
                SELECT COUNT(*)
                FROM vehicle_stat_metric
                WHERE vin = 'VIN001' AND stat_date = DATE '2026-07-01'
                """, Integer.class)).isOne();
    }

    @Test
    void rejectsLegacyPreviousDayCalculationMethod() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource(
                "jdbc:h2:mem:vehicle_stat_metric_legacy;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1",
                "sa",
                ""));
        JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate);
        jdbcTemplate.update("""
                INSERT INTO vehicle_stat_metric
                (vin, stat_date, metric_key, metric_value, metric_unit, calculation_method)
                VALUES ('VIN001', DATE '2026-06-30', 'daily_mileage_km', 8.5, 'km', 'CURRENT_LAST_MINUS_PREVIOUS_LAST')
                """);

        assertThatThrownBy(() -> repository.findDailyStat("VIN001", LocalDate.of(2026, 6, 30)))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("CURRENT_LAST_MINUS_PREVIOUS_LAST");
    }
}
