package at.procon.eventhub.tachographfilesession.service;

import at.procon.eventhub.config.EventHubProperties;
import static org.assertj.core.api.Assertions.assertThat;

import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInterval;
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDerivedProjectionBundle;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.Test;

class DriverTimelineReusableProjectionBuilderTest {

    private final DriverTimelineBuilder legacyBuilder = new DriverTimelineBuilder();
    private final DriverTimelineReusableProjectionBuilder reusableBuilder =
            new DriverTimelineReusableProjectionBuilder(legacyBuilder, new EventHubProperties());

    @Test
    void matchesLegacyDrivingDerivedProjectionChain() {
        DriverExtractionSession driver = new DriverExtractionSession(
                "12:123",
                null,
                null,
                List.of(),
                List.of(),
                List.of(
                        new ExtractedCardVehicleUsageInterval(
                                "CVU-1",
                                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                                100L,
                                200L,
                                "12:REG-1",
                                "VIN-1",
                                "vu-1"
                        ),
                        new ExtractedCardVehicleUsageInterval(
                                "CVU-2",
                                OffsetDateTime.parse("2026-05-02T00:00:00Z"),
                                OffsetDateTime.parse("2026-05-02T02:00:00Z"),
                                201L,
                                260L,
                                "12:REG-2",
                                "VIN-2",
                                "vu-2"
                        )
                ),
                List.of(
                        new ExtractedCardActivityInterval(
                                "ACT-1",
                                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                                "DRIVE",
                                "DRIVER",
                                "INSERTED",
                                "SINGLE",
                                "12:REG-1",
                                "VIN-1",
                                "a"
                        ),
                        new ExtractedCardActivityInterval(
                                "ACT-2",
                                OffsetDateTime.parse("2026-05-02T00:00:00Z"),
                                OffsetDateTime.parse("2026-05-02T00:30:00Z"),
                                "DRIVE",
                                "DRIVER",
                                "INSERTED",
                                "SINGLE",
                                "12:REG-2",
                                "VIN-2",
                                "b"
                        )
                ),
                List.of(),
                List.of()
        );
        TachographFileSession session = new TachographFileSession(
                UUID.randomUUID(),
                new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 2, "42", "b", true, null),
                Map.of(driver.driverKey(), driver),
                new ExtractionStats(1, 2, 2, 1, 1, 0),
                List.of(),
                Instant.now(),
                Instant.now().plus(4, ChronoUnit.HOURS)
        );

        ResolvedDriverTimeline timeline = legacyBuilder.build(session, driver);
        List<TachographEsperDrivingInterruptionIntervalEvent> legacyDrivingInterruptions =
                legacyBuilder.buildEsperDrivingInterruptionIntervalEvents(session.sessionId(), driver.driverKey(), timeline, 3);
        List<TachographEsperDrivingInterruptionIntervalEvent> legacyDailyWeeklyRestCandidates =
                legacyBuilder.buildEsperDailyWeeklyRestCandidateIntervalEvents(legacyDrivingInterruptions, 720);
        List<TachographEsperDrivingInterruptionIntervalEvent> legacyDrivingInterruptionVehicleChanges =
                legacyBuilder.buildEsperDrivingInterruptionVehicleChangeIntervalEvents(legacyDailyWeeklyRestCandidates);
        List<TachographEsperVuCardAbsentIntervalEvent> legacyVuCardAbsentIntervals =
                legacyBuilder.buildEsperVuCardAbsentIntervalEvents(timeline);
        List<TachographEsperPotentialHomeOvernightStayIntervalEvent> legacyPotentialHomeOvernightStays =
                legacyBuilder.buildEsperPotentialHomeOvernightStayIntervalEvents(
                        legacyDrivingInterruptionVehicleChanges,
                        legacyVuCardAbsentIntervals
                );

        TachographEsperDrivingDerivedProjectionBundle reusableBundle =
                reusableBuilder.buildEsperDrivingDerivedProjectionBundle(
                        session.sessionId(),
                        driver.driverKey(),
                        timeline,
                        3,
                        720
                );

        assertThat(reusableBundle.drivingInterruptionIntervals()).containsExactlyElementsOf(legacyDrivingInterruptions);
        assertThat(reusableBundle.dailyWeeklyRestCandidateIntervals()).containsExactlyElementsOf(legacyDailyWeeklyRestCandidates);
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals()).hasSize(1);
        TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent coverageInterval =
                reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0);
        assertThat(coverageInterval.cardAbsentCoveragePercent()).isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
        assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).containsExactlyElementsOf(legacyDrivingInterruptionVehicleChanges);
        assertThat(reusableBundle.vuCardAbsentIntervals()).containsExactlyElementsOf(legacyVuCardAbsentIntervals);
        assertThat(reusableBundle.potentialHomeOvernightStayIntervals()).hasSize(1);
        assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).startedAt())
                .isEqualTo(legacyPotentialHomeOvernightStays.get(0).startedAt());
        assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endedAt())
                .isEqualTo(legacyPotentialHomeOvernightStays.get(0).endedAt());
        assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).cardAbsentCoveragePercent())
                .isEqualTo(legacyPotentialHomeOvernightStays.get(0).cardAbsentCoveragePercent());
        assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).beginBoundaryOdometerKm())
                .isEqualTo(200L);
        assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endBoundaryOdometerKm())
                .isEqualTo(201L);
        assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals()).isEmpty();
    }

    @Test
    void buildsPotentialInVehicleOvernightStayIntervalsFromVehicleUnchangedCoveredRest() {
        DriverExtractionSession driver = new DriverExtractionSession(
                "12:123",
                null,
                null,
                List.of(),
                List.of(),
                List.of(
                        new ExtractedCardVehicleUsageInterval(
                                "CVU-1",
                                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                                OffsetDateTime.parse("2026-05-02T01:00:00Z"),
                                100L,
                                260L,
                                "12:REG-1",
                                "VIN-1",
                                "vu-1"
                        )
                ),
                List.of(
                        new ExtractedCardActivityInterval(
                                "ACT-1",
                                OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                                OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                                "DRIVE",
                                "DRIVER",
                                "INSERTED",
                                "SINGLE",
                                "12:REG-1",
                                "VIN-1",
                                "a"
                        ),
                        new ExtractedCardActivityInterval(
                                "ACT-2",
                                OffsetDateTime.parse("2026-05-02T00:00:00Z"),
                                OffsetDateTime.parse("2026-05-02T00:30:00Z"),
                                "DRIVE",
                                "DRIVER",
                                "INSERTED",
                                "SINGLE",
                                "12:REG-1",
                                "VIN-1",
                                "b"
                        )
                ),
                List.of(
                        new ExtractedSupportEvent(
                                "SUP-1",
                                "12:123",
                                OffsetDateTime.parse("2026-05-01T09:45:00Z"),
                                "POSITION",
                                "POSITION_RECORDED",
                                "SNAPSHOT",
                                null,
                                "12:REG-1",
                                "VIN-1",
                                null,
                                null,
                                null,
                                null,
                                null,
                                new java.math.BigDecimal("48.2082"),
                                new java.math.BigDecimal("16.3738"),
                                null,
                                150L,
                                null,
                                null,
                                null,
                                "geo-1"
                        ),
                        new ExtractedSupportEvent(
                                "SUP-2",
                                "12:123",
                                OffsetDateTime.parse("2026-05-02T00:15:00Z"),
                                "POSITION",
                                "POSITION_RECORDED",
                                "SNAPSHOT",
                                null,
                                "12:REG-1",
                                "VIN-1",
                                null,
                                null,
                                null,
                                null,
                                null,
                                new java.math.BigDecimal("48.2090"),
                                new java.math.BigDecimal("16.3740"),
                                null,
                                150L,
                                null,
                                null,
                                null,
                                "geo-2"
                        )
                ),
                List.of()
        );
        TachographFileSession session = new TachographFileSession(
                UUID.randomUUID(),
                new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 2, "42", "b", true, null),
                Map.of(driver.driverKey(), driver),
                new ExtractionStats(1, 2, 1, 1, 1, 0),
                List.of(),
                Instant.now(),
                Instant.now().plus(4, ChronoUnit.HOURS)
        );

        ResolvedDriverTimeline timeline = legacyBuilder.build(session, driver);
        TachographEsperDrivingDerivedProjectionBundle reusableBundle =
                reusableBuilder.buildEsperDrivingDerivedProjectionBundle(
                        session.sessionId(),
                        driver.driverKey(),
                        timeline,
                        3,
                        720
        );

        assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).isEmpty();
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals()).hasSize(1);
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentCoveragePercent())
                .isEqualTo(0.0d);
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginGeoEventId())
                .isEqualTo("SUP-1");
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginGeoEvent())
                .extracting("eventId", "eventDomain")
                .containsExactly("SUP-1", "POSITION");
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).endGeoEventId())
                .isEqualTo("SUP-2");
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).endGeoEvent())
                .extracting("eventId", "eventDomain")
                .containsExactly("SUP-2", "POSITION");
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginGeoDistanceSeconds())
                .isEqualTo(900L);
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).endGeoDistanceSeconds())
                .isEqualTo(900L);
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginBoundaryOdometerKm())
                .isEqualTo(100L);
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).endBoundaryOdometerKm())
                .isEqualTo(260L);
        assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).geoEvidenceMovementCategory())
                .isEqualTo("STATIONARY");
        assertThat(reusableBundle.potentialHomeOvernightStayIntervals()).isEmpty();
        assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals()).hasSize(1);
        assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).startedAt())
                .isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
        assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).endedAt())
                .isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
        assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEventId())
                .isEqualTo("SUP-1");
        assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEvent())
                .extracting("eventId", "eventDomain")
                .containsExactly("SUP-1", "POSITION");
    }
}
