package at.procon.eventhub.tachographfilesession.service;

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

import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.service.EventDetailsFactory;
import at.procon.eventhub.tachographfilesession.dto.TachographEsperEventsProcessingRequest;
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingRequest;
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingResultDto;
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.ExtractionStats;
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 com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

class TachographFileSessionProcessingServiceTest {

    @Test
    void returnsEsperDriverProcessingResultsFromSessionTimeline() {
        EventHubProperties properties = new EventHubProperties();
        TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
        DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
        TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
                repository,
                driverTimelineBuilder,
                new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
                new EventBackedDriverTimelineBuilder(
                        new IntervalBackedDriverTimelineEventBuilder(
                                driverTimelineBuilder,
                                new DriverKeyFactory(),
                                new VehicleKeyFactory(),
                                new EventDetailsFactory(new ObjectMapper())
                        )
                ),
                properties
        );

        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-01T11:00:00Z"),
                                100L,
                                200L,
                                "12:REG-1",
                                "VIN-1",
                                "vu-1"
                        ),
                        new ExtractedCardVehicleUsageInterval(
                                "CVU-2",
                                OffsetDateTime.parse("2026-05-01T12:00:00Z"),
                                OffsetDateTime.parse("2026-05-01T13:00:00Z"),
                                201L,
                                260L,
                                "12:REG-2",
                                "VIN-2",
                                "vu-2"
                        )
                ),
                List.of(
                        new ExtractedCardActivityInterval("ACT-1", OffsetDateTime.parse("2026-05-01T08:30:00Z"), OffsetDateTime.parse("2026-05-01T09:00:00Z"), "WORK", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "a"),
                        new ExtractedCardActivityInterval("ACT-2", OffsetDateTime.parse("2026-05-01T09:00:00Z"), OffsetDateTime.parse("2026-05-01T10:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "b")
                ),
                List.of(),
                List.of()
        );
        TachographFileSession session = new TachographFileSession(
                UUID.randomUUID(),
                new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 3, "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)
        );
        repository.save(session);

        TachographEsperDriverProcessingResultDto result =
                service.getEsperDriverProcessingResults(session.sessionId(), driver.driverKey());

        assertThat(result.sourceKind()).isEqualTo("DRIVER_CARD");
        assertThat(result.activityIntervalCount()).isEqualTo(2);
        assertThat(result.drivingIntervalCount()).isEqualTo(1);
        assertThat(result.drivingInterruptionIntervalCount()).isEqualTo(0);
        assertThat(result.drivingInterruptionVehicleChangeIntervalCount()).isEqualTo(0);
        assertThat(result.dailyWeeklyRestCandidateIntervalCount()).isEqualTo(0);
        assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
        assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
        assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
        assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
        assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
        assertThat(result.vuCardAbsentIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:01Z"));
        assertThat(result.vuCardAbsentIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T12:00:00Z"));
    }

    @Test
    void appliesOccurredWindowToEsperDriverProcessingResults() {
        EventHubProperties properties = new EventHubProperties();
        TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
        DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
        TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
                repository,
                driverTimelineBuilder,
                new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
                new EventBackedDriverTimelineBuilder(
                        new IntervalBackedDriverTimelineEventBuilder(
                                driverTimelineBuilder,
                                new DriverKeyFactory(),
                                new VehicleKeyFactory(),
                                new EventDetailsFactory(new ObjectMapper())
                        )
                ),
                properties
        );

        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-01T11:00:00Z"),
                                100L,
                                200L,
                                "12:REG-1",
                                "VIN-1",
                                "vu-1"
                        ),
                        new ExtractedCardVehicleUsageInterval(
                                "CVU-2",
                                OffsetDateTime.parse("2026-05-01T12:00:00Z"),
                                OffsetDateTime.parse("2026-05-01T13:00:00Z"),
                                201L,
                                260L,
                                "12:REG-2",
                                "VIN-2",
                                "vu-2"
                        )
                ),
                List.of(
                        new ExtractedCardActivityInterval("ACT-1", OffsetDateTime.parse("2026-05-01T08:30:00Z"), OffsetDateTime.parse("2026-05-01T09:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "a"),
                        new ExtractedCardActivityInterval("ACT-2", OffsetDateTime.parse("2026-05-01T09:00:00Z"), OffsetDateTime.parse("2026-05-01T10:00:00Z"), "WORK", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "b"),
                        new ExtractedCardActivityInterval("ACT-3", OffsetDateTime.parse("2026-05-01T10:00:00Z"), OffsetDateTime.parse("2026-05-01T10:05:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-2", "VIN-2", "c")
                ),
                List.of(),
                List.of()
        );
        TachographFileSession session = new TachographFileSession(
                UUID.randomUUID(),
                new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 3, "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)
        );
        repository.save(session);

        TachographEsperDriverProcessingResultDto result = service.getEsperDriverProcessingResults(
                session.sessionId(),
                driver.driverKey(),
                new TachographEsperEventsProcessingRequest(
                        OffsetDateTime.parse("2026-05-01T08:45:00Z"),
                        OffsetDateTime.parse("2026-05-01T12:30:00Z"),
                        3,
                        720
                )
        );

        assertThat(result.requestedFrom()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:45:00Z"));
        assertThat(result.requestedTo()).isEqualTo(OffsetDateTime.parse("2026-05-01T12:30:00Z"));
        assertThat(result.activityIntervalCount()).isEqualTo(3);
        assertThat(result.activityIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:45:00Z"));
        assertThat(result.activityIntervals().get(0).clippedToRequestedPeriod()).isTrue();
        assertThat(result.drivingIntervalCount()).isEqualTo(2);
        assertThat(result.drivingInterruptionIntervalCount()).isEqualTo(1);
        assertThat(result.drivingInterruptionVehicleChangeIntervalCount()).isEqualTo(0);
        assertThat(result.dailyWeeklyRestCandidateIntervalCount()).isEqualTo(0);
        assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
        assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
        assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
        assertThat(result.drivingInterruptionIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T09:00:00Z"));
        assertThat(result.drivingInterruptionIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
        assertThat(result.drivingInterruptionIntervals().get(0).previousRegistrationKey()).isEqualTo("12:REG-1");
        assertThat(result.drivingInterruptionIntervals().get(0).nextRegistrationKey()).isEqualTo("12:REG-2");
        assertThat(result.drivingInterruptionIntervals().get(0).previousVehicleKey()).isEqualTo("VIN-1");
        assertThat(result.drivingInterruptionIntervals().get(0).nextVehicleKey()).isEqualTo("VIN-2");
        assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
        assertThat(result.vehicleUsageIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:45:00Z"));
        assertThat(result.vehicleUsageIntervals().get(0).odometerBeginKm()).isNull();
        assertThat(result.vehicleUsageIntervals().get(1).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T12:30:00Z"));
        assertThat(result.vehicleUsageIntervals().get(1).odometerEndKm()).isNull();
        assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
    }

    @Test
    void canUseEventBackedTimelineModeForEsperProcessing() {
        EventHubProperties properties = new EventHubProperties();
        properties.getTachographFileSession().getProcessing().setTimelineInputMode(EventHubProperties.TimelineInputMode.EVENTS);
        TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
        DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
        TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
                repository,
                driverTimelineBuilder,
                new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
                new EventBackedDriverTimelineBuilder(
                        new IntervalBackedDriverTimelineEventBuilder(
                                driverTimelineBuilder,
                                new DriverKeyFactory(),
                                new VehicleKeyFactory(),
                                new EventDetailsFactory(new ObjectMapper())
                        )
                ),
                properties
        );

        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-01T11:00:00Z"),
                                100L,
                                200L,
                                "12:REG-1",
                                "VIN-1",
                                "vu-1"
                        ),
                        new ExtractedCardVehicleUsageInterval(
                                "CVU-2",
                                OffsetDateTime.parse("2026-05-01T12:00:00Z"),
                                OffsetDateTime.parse("2026-05-01T13:00:00Z"),
                                201L,
                                260L,
                                "12:REG-2",
                                "VIN-2",
                                "vu-2"
                        )
                ),
                List.of(
                        new ExtractedCardActivityInterval("ACT-1", OffsetDateTime.parse("2026-05-01T08:30:00Z"), OffsetDateTime.parse("2026-05-01T09:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "a"),
                        new ExtractedCardActivityInterval("ACT-2", OffsetDateTime.parse("2026-05-01T09:00:00Z"), OffsetDateTime.parse("2026-05-01T10:00:00Z"), "WORK", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "b"),
                        new ExtractedCardActivityInterval("ACT-3", OffsetDateTime.parse("2026-05-01T10:00:00Z"), OffsetDateTime.parse("2026-05-01T10:05:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-2", "VIN-2", "c")
                ),
                List.of(),
                List.of()
        );
        TachographFileSession session = new TachographFileSession(
                UUID.randomUUID(),
                new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 3, "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)
        );
        repository.save(session);

        TachographEsperDriverProcessingResultDto result = service.getEsperDriverProcessingResults(
                session.sessionId(),
                driver.driverKey(),
                new TachographEsperEventsProcessingRequest(
                        OffsetDateTime.parse("2026-05-01T08:45:00Z"),
                        OffsetDateTime.parse("2026-05-01T12:30:00Z"),
                        3,
                        720
                )
        );

        assertThat(result.activityIntervalCount()).isEqualTo(3);
        assertThat(result.drivingIntervalCount()).isEqualTo(2);
        assertThat(result.drivingInterruptionIntervalCount()).isEqualTo(1);
        assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
        assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
        assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
        assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
    }

    @Test
    void returnsPotentialHomeOvernightStayIntervalsWhenVuCardAbsentCoversLongDti() {
        EventHubProperties properties = new EventHubProperties();
        TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
        DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
        TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
                repository,
                driverTimelineBuilder,
                new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
                new EventBackedDriverTimelineBuilder(
                        new IntervalBackedDriverTimelineEventBuilder(
                                driverTimelineBuilder,
                                new DriverKeyFactory(),
                                new VehicleKeyFactory(),
                                new EventDetailsFactory(new ObjectMapper())
                        )
                ),
                properties
        );

        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-1",
                                "VIN-1",
                                "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)
        );
        repository.save(session);

        TachographEsperDriverProcessingResultDto result = service.getEsperDriverProcessingResults(
                session.sessionId(),
                driver.driverKey(),
                new TachographEsperEventsProcessingRequest(
                        OffsetDateTime.parse("2026-05-01T11:00:00Z"),
                        OffsetDateTime.parse("2026-05-01T23:00:00Z"),
                        3,
                        720
                )
        );

        assertThat(result.dailyWeeklyRestCandidateIntervalCount()).isEqualTo(1);
        assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1);
        assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
        assertThat(result.drivingInterruptionVehicleChangeIntervalCount()).isEqualTo(1);
        assertThat(result.dailyWeeklyRestCandidateIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
        assertThat(result.dailyWeeklyRestCandidateIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T23:00:00Z"));
        assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
        assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(50_399L);
        assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentCoveragePercent())
                .isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
        assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
        assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).previousRegistrationKey()).isEqualTo("12:REG-1");
        assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).nextRegistrationKey()).isEqualTo("12:REG-2");
        assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(1);
        assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
        assertThat(result.potentialHomeOvernightStayIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
        assertThat(result.potentialHomeOvernightStayIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T23:00:00Z"));
        assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(50_399L);
        assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardAbsentCoveragePercent())
                .isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
    }

    @Test
    void returnsPotentialInVehicleOvernightStayIntervalsWhenVehicleUnchangedAndCardRemainsInserted() {
        EventHubProperties properties = new EventHubProperties();
        TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
        DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
        TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
                repository,
                driverTimelineBuilder,
                new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
                new EventBackedDriverTimelineBuilder(
                        new IntervalBackedDriverTimelineEventBuilder(
                                driverTimelineBuilder,
                                new DriverKeyFactory(),
                                new VehicleKeyFactory(),
                                new EventDetailsFactory(new ObjectMapper())
                        )
                ),
                properties
        );

        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(),
                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)
        );
        repository.save(session);

        TachographEsperDriverProcessingResultDto result = service.getEsperDriverProcessingResults(
                session.sessionId(),
                driver.driverKey(),
                new TachographEsperEventsProcessingRequest(
                        OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                        OffsetDateTime.parse("2026-05-02T00:00:00Z"),
                        3,
                        720
                )
        );

        assertThat(result.dailyWeeklyRestCandidateIntervalCount()).isEqualTo(1);
        assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1);
        assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
        assertThat(result.drivingInterruptionVehicleChangeIntervalCount()).isEqualTo(0);
        assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(1);
        assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
        assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(0L);
        assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
        assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
        assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(0L);
        assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardAbsentCoveragePercent()).isEqualTo(0.0d);
    }

    @Test
    void returnsUnclassifiedDailyWeeklyRestCandidateCoverageIntervalsWhenRestCandidateFitsNeitherCategory() {
        EventHubProperties properties = new EventHubProperties();
        TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
        DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
        TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
                repository,
                driverTimelineBuilder,
                new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
                new EventBackedDriverTimelineBuilder(
                        new IntervalBackedDriverTimelineEventBuilder(
                                driverTimelineBuilder,
                                new DriverKeyFactory(),
                                new VehicleKeyFactory(),
                                new EventDetailsFactory(new ObjectMapper())
                        )
                ),
                properties
        );

        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-01T14:00:00Z"),
                                100L,
                                200L,
                                "12:REG-1",
                                "VIN-1",
                                "vu-1"
                        ),
                        new ExtractedCardVehicleUsageInterval(
                                "CVU-2",
                                OffsetDateTime.parse("2026-05-01T18:00:00Z"),
                                OffsetDateTime.parse("2026-05-02T02:00:00Z"),
                                201L,
                                260L,
                                "12:REG-1",
                                "VIN-1",
                                "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:30:00Z"), OffsetDateTime.parse("2026-05-02T01:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "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)
        );
        repository.save(session);

        TachographEsperDriverProcessingResultDto result = service.getEsperDriverProcessingResults(
                session.sessionId(),
                driver.driverKey(),
                new TachographEsperEventsProcessingRequest(
                        OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                        OffsetDateTime.parse("2026-05-02T00:30:00Z"),
                        3,
                        720
                )
        );

        assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1);
        assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
        assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
        assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1);
        assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).startedAt())
                .isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
        assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).endedAt())
                .isEqualTo(OffsetDateTime.parse("2026-05-02T00:30:00Z"));
        assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentCoveragePercent())
                .isLessThan(95.0d);
    }

    @Test
    void returnsPotentialInVehicleTripIntervalsForSameVehicleOvernightSequenceBoundedByCoverageIntervals() {
        EventHubProperties properties = new EventHubProperties();
        TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
        DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
        TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
                repository,
                driverTimelineBuilder,
                new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
                new EventBackedDriverTimelineBuilder(
                        new IntervalBackedDriverTimelineEventBuilder(
                                driverTimelineBuilder,
                                new DriverKeyFactory(),
                                new VehicleKeyFactory(),
                                new EventDetailsFactory(new ObjectMapper())
                        )
                ),
                properties
        );

        DriverExtractionSession driver = new DriverExtractionSession(
                "12:123",
                null,
                null,
                List.of(),
                List.of(),
                List.of(
                        new ExtractedCardVehicleUsageInterval("CVU-0", OffsetDateTime.parse("2026-05-01T08:00:00Z"), OffsetDateTime.parse("2026-05-01T09:00:00Z"), 100L, 150L, "12:REG-0", "VIN-0", "vu-0"),
                        new ExtractedCardVehicleUsageInterval("CVU-1", OffsetDateTime.parse("2026-05-01T22:00:00Z"), OffsetDateTime.parse("2026-05-04T02:00:00Z"), 200L, 400L, "12:REG-1", "VIN-1", "vu-1"),
                        new ExtractedCardVehicleUsageInterval("CVU-2", OffsetDateTime.parse("2026-05-05T00:00:00Z"), OffsetDateTime.parse("2026-05-05T02:00:00Z"), 401L, 430L, "12:REG-2", "VIN-2", "vu-2")
                ),
                List.of(
                        new ExtractedCardActivityInterval("ACT-0", OffsetDateTime.parse("2026-05-01T08:00:00Z"), OffsetDateTime.parse("2026-05-01T09:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-0", "VIN-0", "a"),
                        new ExtractedCardActivityInterval("ACT-1", OffsetDateTime.parse("2026-05-01T22:00:00Z"), OffsetDateTime.parse("2026-05-02T00:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "b"),
                        new ExtractedCardActivityInterval("ACT-2", OffsetDateTime.parse("2026-05-02T20:00:00Z"), OffsetDateTime.parse("2026-05-02T22:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "c"),
                        new ExtractedCardActivityInterval("ACT-3", OffsetDateTime.parse("2026-05-03T20:00:00Z"), OffsetDateTime.parse("2026-05-03T22:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "d"),
                        new ExtractedCardActivityInterval("ACT-4", OffsetDateTime.parse("2026-05-05T00:00:00Z"), OffsetDateTime.parse("2026-05-05T02:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-2", "VIN-2", "e")
                ),
                List.of(),
                List.of()
        );
        TachographFileSession session = new TachographFileSession(
                UUID.randomUUID(),
                new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 5, "42", "b", true, null),
                Map.of(driver.driverKey(), driver),
                new ExtractionStats(1, 5, 3, 1, 1, 0),
                List.of(),
                Instant.now(),
                Instant.now().plus(4, ChronoUnit.HOURS)
        );
        repository.save(session);

        TachographEsperDriverProcessingResultDto result = service.getEsperDriverProcessingResults(
                session.sessionId(),
                driver.driverKey(),
                new TachographEsperEventsProcessingRequest(
                        OffsetDateTime.parse("2026-05-01T10:00:00Z"),
                        OffsetDateTime.parse("2026-05-05T00:00:00Z"),
                        3,
                        720
                )
        );

        assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(2);
        assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(1);
        assertThat(result.potentialInVehicleTripIntervals().get(0).startedAt())
                .isEqualTo(OffsetDateTime.parse("2026-05-01T22:00:00Z"));
        assertThat(result.potentialInVehicleTripIntervals().get(0).endedAt())
                .isEqualTo(OffsetDateTime.parse("2026-05-03T22:00:00Z"));
        assertThat(result.potentialInVehicleTripIntervals().get(0).containedPotentialInVehicleOvernightStayIntervalCount()).isEqualTo(2);
        assertThat(result.potentialInVehicleTripIntervals().get(0).potentialInVehicleOvernightStayIntervals()).hasSize(2);
        assertThat(result.potentialInVehicleTripIntervals().get(0).registrationKey()).isEqualTo("12:REG-1");
    }

    @Test
    void evaluatesOperatingPeriodsFromSessionTimeline() {
        EventHubProperties properties = new EventHubProperties();
        TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
        DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
        TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
                repository,
                driverTimelineBuilder,
                new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
                new EventBackedDriverTimelineBuilder(
                        new IntervalBackedDriverTimelineEventBuilder(
                                driverTimelineBuilder,
                                new DriverKeyFactory(),
                                new VehicleKeyFactory(),
                                new EventDetailsFactory(new ObjectMapper())
                        )
                ),
                properties
        );

        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-01T20:00:00Z"),
                        100L,
                        200L,
                        "12:REG-1",
                        "VIN-1",
                        "vu"
                )),
                List.of(
                        new ExtractedCardActivityInterval("ACT-1", OffsetDateTime.parse("2026-05-01T08:00:00Z"), OffsetDateTime.parse("2026-05-01T08:30:00Z"), "BREAK_REST", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "a"),
                        new ExtractedCardActivityInterval("ACT-2", OffsetDateTime.parse("2026-05-01T08:30:00Z"), OffsetDateTime.parse("2026-05-01T09:00:00Z"), "WORK", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "b"),
                        new ExtractedCardActivityInterval("ACT-3", OffsetDateTime.parse("2026-05-01T09:00:00Z"), OffsetDateTime.parse("2026-05-01T09:20:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "c"),
                        new ExtractedCardActivityInterval("ACT-4", OffsetDateTime.parse("2026-05-01T09:20:00Z"), OffsetDateTime.parse("2026-05-01T10:00:00Z"), "BREAK_REST", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "d"),
                        new ExtractedCardActivityInterval("ACT-5", OffsetDateTime.parse("2026-05-01T10:00:00Z"), OffsetDateTime.parse("2026-05-01T10:15:00Z"), "WORK", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "e"),
                        new ExtractedCardActivityInterval("ACT-6", OffsetDateTime.parse("2026-05-01T10:15:00Z"), OffsetDateTime.parse("2026-05-01T10:35:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "f"),
                        new ExtractedCardActivityInterval("ACT-7", OffsetDateTime.parse("2026-05-01T18:45:00Z"), OffsetDateTime.parse("2026-05-01T19:00:00Z"), "WORK", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "g")
                ),
                List.of(),
                List.of()
        );
        TachographFileSession session = new TachographFileSession(
                UUID.randomUUID(),
                new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 3, "42", "b", true, null),
                Map.of(driver.driverKey(), driver),
                new ExtractionStats(1, 7, 1, 1, 1, 0),
                List.of(),
                Instant.now(),
                Instant.now().plus(4, ChronoUnit.HOURS)
        );
        repository.save(session);

        TachographOperatingPeriodsProcessingResultDto result = service.evaluateOperatingPeriods(
                session.sessionId(),
                driver.driverKey(),
                new TachographOperatingPeriodsProcessingRequest(
                        OffsetDateTime.parse("2026-05-01T08:00:00Z"),
                        OffsetDateTime.parse("2026-05-01T19:00:00Z"),
                        7,
                        10,
                        0,
                        0
                )
        );

        assertThat(result.timeline().sourceKind()).isEqualTo("DRIVER_CARD");
        assertThat(result.evaluationIntervals()).extracting("activityType").contains("WORK", "DRIVE");
        assertThat(result.operatingPeriods()).hasSize(2);
        assertThat(result.operatingPeriods().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:30:00Z"));
        assertThat(result.operatingPeriods().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:35:00Z"));
        assertThat(result.operatingPeriods().get(0).closedBy()).isEqualTo("UNKNOWN_GAP");
        assertThat(result.operatingPeriods().get(0).drivingTimeInterruptionEvaluation().departureAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T09:00:00Z"));
        assertThat(result.operatingPeriods().get(0).drivingTimeInterruptionEvaluation().interruptionsBetweenSignificantDrivingPeriods()).hasSize(1);
        assertThat(result.operatingPeriods().get(0).drivingTimeInterruptionEvaluation().interruptionsBetweenSignificantDrivingPeriods().get(0).from())
                .isEqualTo(OffsetDateTime.parse("2026-05-01T09:20:00Z"));
        assertThat(result.operatingPeriods().get(1).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T18:45:00Z"));
        assertThat(result.operatingPeriods().get(1).closedBy()).isEqualTo("FLUSH");
    }
}
