package at.procon.eventhub.esperpoc.service;

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

import at.procon.eventhub.esperpoc.dto.ActivityIntervalDto;
import at.procon.eventhub.esperpoc.dto.EsperActivityMergeMode;
import at.procon.eventhub.esperpoc.dto.EsperPocRequest;
import at.procon.eventhub.esperpoc.dto.EsperShiftResolutionMode;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.Test;

class EsperPocDriverCardActivityServiceTest {

    private final EsperDriverActivityEngine engine = new EsperDriverActivityEngine();
    private final EsperPocDriverCardActivityService service = new EsperPocDriverCardActivityService(null, engine);

    @Test
    void mergesIdenticalActivitiesAcrossUtcMidnight() {
        UUID driverId = UUID.randomUUID();
        ActivityIntervalDto beforeMidnight = ActivityIntervalDto.raw(
                driverId,
                null,
                null,
                "BREAK_REST",
                "DRIVER",
                "INSERTED",
                "SINGLE",
                "DRIVER_CARD",
                OffsetDateTime.parse("2026-04-30T23:50:00Z"),
                OffsetDateTime.parse("2026-05-01T00:00:00Z"),
                "1"
        );
        ActivityIntervalDto afterMidnight = ActivityIntervalDto.raw(
                driverId,
                null,
                null,
                "BREAK_REST",
                "DRIVER",
                "INSERTED",
                "SINGLE",
                "DRIVER_CARD",
                OffsetDateTime.parse("2026-05-01T00:00:00Z"),
                OffsetDateTime.parse("2026-05-01T00:20:00Z"),
                "2"
        );

        var merged = service.mergeConsecutiveIdenticalActivities(
                List.of(beforeMidnight, afterMidnight),
                Duration.ZERO
        );

        assertThat(merged).hasSize(1);
        assertThat(merged.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-30T23:50:00Z"));
        assertThat(merged.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T00:20:00Z"));
        assertThat(merged.get(0).durationSeconds()).isEqualTo(30 * 60L);
        assertThat(merged.get(0).sourceRowIds()).containsExactly("1", "2");
    }

    @Test
    void splitsOperatingPeriodsByBreakRestLongerThanConfiguredHoursAndEvaluatesDepartureArrivalPerPeriod() {
        UUID driverId = UUID.randomUUID();
        EsperPocRequest request = request(driverId, null, null);

        List<ActivityIntervalDto> activities = List.of(
                activity(driverId, "DRIVE", "2026-04-01T06:00:00Z", "2026-04-01T08:00:00Z", "d1", "DRIVER_CARD"),
                activity(driverId, "WORK", "2026-04-01T08:00:00Z", "2026-04-01T09:00:00Z", "w1", "DRIVER_CARD"),
                activity(driverId, "DRIVE", "2026-04-01T09:30:00Z", "2026-04-01T11:00:00Z", "d2", "DRIVER_CARD"),
                activity(driverId, "BREAK_REST", "2026-04-01T20:00:00Z", "2026-04-02T04:30:00Z", "r1", "DRIVER_CARD"),
                activity(driverId, "DRIVE", "2026-04-02T05:00:00Z", "2026-04-02T07:00:00Z", "d3", "DRIVER_CARD"),
                activity(driverId, "BREAK_REST", "2026-04-02T10:00:00Z", "2026-04-02T10:30:00Z", "r2", "DRIVER_CARD"),
                activity(driverId, "DRIVE", "2026-04-02T10:30:00Z", "2026-04-02T12:00:00Z", "d4", "DRIVER_CARD")
        );

        var periods = service.buildOperatingTimePeriods(
                request,
                request.occurredFrom(),
                request.occurredTo(),
                activities
        );

        assertThat(periods).hasSize(2);
        assertThat(periods.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T06:00:00Z"));
        assertThat(periods.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T11:00:00Z"));
        assertThat(periods.get(0).drivingTimeInterruptionEvaluation().departureAt())
                .isEqualTo(OffsetDateTime.parse("2026-04-01T06:00:00Z"));
        assertThat(periods.get(0).drivingTimeInterruptionEvaluation().arrivalAt())
                .isEqualTo(OffsetDateTime.parse("2026-04-01T11:00:00Z"));
        assertThat(periods.get(0).drivingTimeInterruptionEvaluation().interruptionsBetweenSignificantDrivingPeriods())
                .hasSize(1);

        assertThat(periods.get(1).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-02T05:00:00Z"));
        assertThat(periods.get(1).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-02T12:00:00Z"));
        assertThat(periods.get(1).drivingTimeInterruptionEvaluation().departureAt())
                .isEqualTo(OffsetDateTime.parse("2026-04-02T05:00:00Z"));
        assertThat(periods.get(1).drivingTimeInterruptionEvaluation().arrivalAt())
                .isEqualTo(OffsetDateTime.parse("2026-04-02T12:00:00Z"));
        assertThat(periods.get(1).workingOperationTimes().breakRestSeconds()).isEqualTo(30 * 60L);
    }

    @Test
    void usesVuIntervalsOnlyForUncoveredDriverCardGaps() {
        UUID driverId = UUID.randomUUID();
        List<ActivityIntervalDto> resolved = service.resolveVuFillGaps(
                List.of(
                        activity(driverId, "DRIVE", "2026-04-01T08:00:00Z", "2026-04-01T10:00:00Z", "c1", "DRIVER_CARD")
                ),
                List.of(
                        activity(driverId, "DRIVE", "2026-04-01T09:00:00Z", "2026-04-01T11:00:00Z", "v1", "VEHICLE_UNIT")
                )
        );

        assertThat(resolved).hasSize(2);
        assertThat(resolved.get(0).sourceKind()).isEqualTo("DRIVER_CARD");
        assertThat(resolved.get(1).sourceKind()).isEqualTo("VEHICLE_UNIT");
        assertThat(resolved.get(1).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T10:00:00Z"));
        assertThat(resolved.get(1).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T11:00:00Z"));
    }

    @Test
    void resolvesTwoWorkingShiftsAcrossLongRestGap() {
        UUID driverId = UUID.randomUUID();
        EsperPocRequest request = request(driverId, null, null);

        var shifts = service.buildResolvedWorkShifts(
                request,
                request.occurredFrom(),
                request.occurredTo(),
                List.of(
                        activity(driverId, "DRIVE", "2026-04-01T06:00:00Z", "2026-04-01T10:00:00Z", "d1", "DRIVER_CARD"),
                        activity(driverId, "BREAK_REST", "2026-04-01T10:00:00Z", "2026-04-01T17:30:00Z", "r1", "DRIVER_CARD"),
                        activity(driverId, "WORK", "2026-04-01T17:30:00Z", "2026-04-01T19:00:00Z", "w1", "VEHICLE_UNIT")
                )
        );

        assertThat(shifts).hasSize(2);
        assertThat(shifts.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T06:00:00Z"));
        assertThat(shifts.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T10:00:00Z"));
        assertThat(shifts.get(0).dailyRestingTimeSeconds()).isEqualTo(7L * 60L * 60L + 30L * 60L);
        assertThat(shifts.get(1).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T17:30:00Z"));
        assertThat(shifts.get(1).usedDataKind()).isEqualTo("VEHICLE_UNIT");
    }

    @Test
    void esperMergeModeMatchesJavaMergeMode() {
        UUID driverId = UUID.randomUUID();
        List<ActivityIntervalDto> activities = List.of(
                activity(driverId, "BREAK_REST", "2026-04-01T08:00:00Z", "2026-04-01T09:00:00Z", "r1", "DRIVER_CARD"),
                activity(driverId, "BREAK_REST", "2026-04-01T09:00:00Z", "2026-04-01T10:00:00Z", "r2", "DRIVER_CARD"),
                activity(driverId, "WORK", "2026-04-01T10:05:00Z", "2026-04-01T11:00:00Z", "w1", "DRIVER_CARD")
        );

        List<ActivityIntervalDto> javaMerged = service.mergeConsecutiveIdenticalActivities(
                activities,
                Duration.ofSeconds(60)
        );
        List<ActivityIntervalDto> esperMerged = engine.mergeConsecutiveIdenticalActivities(
                activities,
                Duration.ofSeconds(60)
        );

        assertThat(esperMerged).usingRecursiveComparison().isEqualTo(javaMerged);
    }

    @Test
    void esperShiftResolutionModeMatchesJavaShiftResolutionMode() {
        UUID driverId = UUID.randomUUID();
        EsperPocRequest javaRequest = request(driverId, EsperActivityMergeMode.JAVA, EsperShiftResolutionMode.JAVA);
        EsperPocRequest esperRequest = request(driverId, EsperActivityMergeMode.JAVA, EsperShiftResolutionMode.ESPER);
        List<ActivityIntervalDto> intervals = List.of(
                activity(driverId, "DRIVE", "2026-04-01T06:00:00Z", "2026-04-01T10:00:00Z", "d1", "DRIVER_CARD"),
                activity(driverId, "BREAK_REST", "2026-04-01T10:00:00Z", "2026-04-01T17:30:00Z", "r1", "DRIVER_CARD"),
                activity(driverId, "WORK", "2026-04-01T17:30:00Z", "2026-04-01T19:00:00Z", "w1", "VEHICLE_UNIT")
        );

        var javaShifts = service.buildResolvedWorkShifts(
                javaRequest,
                javaRequest.occurredFrom(),
                javaRequest.occurredTo(),
                intervals
        );
        var esperShifts = service.buildResolvedWorkShifts(
                esperRequest,
                esperRequest.occurredFrom(),
                esperRequest.occurredTo(),
                intervals
        );

        assertThat(esperShifts).usingRecursiveComparison().isEqualTo(javaShifts);
    }

    private EsperPocRequest request(
            UUID driverId,
            EsperActivityMergeMode activityMergeMode,
            EsperShiftResolutionMode shiftResolutionMode
    ) {
        return new EsperPocRequest(
                "default",
                driverId,
                OffsetDateTime.parse("2026-04-01T00:00:00Z"),
                OffsetDateTime.parse("2026-04-03T00:00:00Z"),
                24,
                3,
                60,
                7,
                420,
                5,
                activityMergeMode,
                shiftResolutionMode
        );
    }

    private ActivityIntervalDto activity(
            UUID driverId,
            String activity,
            String from,
            String to,
            String sourceRowId,
            String sourceKind
    ) {
        return ActivityIntervalDto.raw(
                driverId,
                null,
                null,
                activity,
                "DRIVER",
                "INSERTED",
                "KNOWN",
                sourceKind,
                OffsetDateTime.parse(from),
                OffsetDateTime.parse(to),
                sourceRowId
        ).asMerged(List.of(sourceRowId));
    }
}
