package at.procon.eventhub.processing.service;

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

import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.EventDomain;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.dto.EventLifecycle;
import at.procon.eventhub.dto.EventType;
import at.procon.eventhub.dto.VehicleRefDto;
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeClassifier;
import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.Test;

class RuntimeDriverVehicleEvidenceAttachmentServiceTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final RuntimeDriverVehicleEvidenceAttachmentService service = new RuntimeDriverVehicleEvidenceAttachmentService(
            new UnifiedEventTimelineReconstructor(),
            new RuntimeEventScopeClassifier()
    );

    @Test
    void attachesVehicleOnlyEvidenceInsideDriverVehicleUsageInterval() {
        List<EventHubEventDto> driverEvents = List.of(
                cardEvent("card-1-in", EventType.CARD_INSERTED, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T08:00:00Z"),
                cardEvent("card-1-out", EventType.CARD_WITHDRAWN, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T18:00:00Z")
        );
        EventHubEventDto vehicleEvidence = vehicleOnlyEvent("pos-inside", "VIN-1", "AT:W-1", "2026-05-01T12:00:00Z");
        EventHubEventDto outside = vehicleOnlyEvent("pos-outside", "VIN-1", "AT:W-1", "2026-05-01T20:00:00Z");
        EventHubEventDto wrongVehicle = vehicleOnlyEvent("pos-wrong", "VIN-2", "AT:W-2", "2026-05-01T12:00:00Z");

        RuntimeDriverVehicleEvidenceAttachmentResult result = service.attachVehicleEvidence(
                "DRIVER-1",
                driverEvents,
                List.of(driverEvents.get(0), driverEvents.get(1), vehicleEvidence, outside, wrongVehicle),
                true,
                0
        );

        assertThat(result.vehicleUsageIntervalCount()).isEqualTo(1);
        assertThat(result.candidateVehicleEvidenceEventCount()).isEqualTo(3);
        assertThat(result.attachedVehicleEvidenceEvents()).extracting(EventHubEventDto::externalSourceEventId)
                .containsExactly("pos-inside");
        assertThat(result.ignoredVehicleEvidenceEventCount()).isEqualTo(2);
        assertThat(result.mergedEvents()).extracting(EventHubEventDto::externalSourceEventId)
                .containsExactly("card-1-in", "pos-inside", "card-1-out");
    }

    @Test
    void usesConfiguredPaddingWhenAttachingVehicleEvidence() {
        List<EventHubEventDto> driverEvents = List.of(
                cardEvent("card-1-in", EventType.CARD_INSERTED, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T08:00:00Z"),
                cardEvent("card-1-out", EventType.CARD_WITHDRAWN, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T18:00:00Z")
        );
        EventHubEventDto justAfter = vehicleOnlyEvent("pos-after", "VIN-1", "AT:W-1", "2026-05-01T18:10:00Z");

        RuntimeDriverVehicleEvidenceAttachmentResult result = service.attachVehicleEvidence(
                "DRIVER-1",
                driverEvents,
                List.of(driverEvents.get(0), driverEvents.get(1), justAfter),
                true,
                15
        );

        assertThat(result.attachedVehicleEvidenceEvents()).extracting(EventHubEventDto::externalSourceEventId)
                .containsExactly("pos-after");
    }

    @Test
    void doesNotAttachVehicleEvidenceWhenDisabled() {
        List<EventHubEventDto> driverEvents = List.of(
                cardEvent("card-1-in", EventType.CARD_INSERTED, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T08:00:00Z"),
                cardEvent("card-1-out", EventType.CARD_WITHDRAWN, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T18:00:00Z")
        );
        EventHubEventDto vehicleEvidence = vehicleOnlyEvent("pos-inside", "VIN-1", "AT:W-1", "2026-05-01T12:00:00Z");

        RuntimeDriverVehicleEvidenceAttachmentResult result = service.attachVehicleEvidence(
                "DRIVER-1",
                driverEvents,
                List.of(driverEvents.get(0), driverEvents.get(1), vehicleEvidence),
                false,
                0
        );

        assertThat(result.attachedVehicleEvidenceEvents()).isEmpty();
        assertThat(result.mergedEvents()).extracting(EventHubEventDto::externalSourceEventId)
                .containsExactly("card-1-in", "card-1-out");
    }

    @Test
    void mergesMidnightVehicleUsageContinuationBeforeEvidenceAttachment() {
        List<EventHubEventDto> driverEvents = List.of(
                cardEvent("card-1-in", EventType.CARD_INSERTED, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T20:00:00Z"),
                cardEvent("card-1-out", EventType.CARD_WITHDRAWN, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T23:59:59Z"),
                cardEvent("card-2-in", EventType.CARD_INSERTED, "DRIVER-1", "interval-2", "VIN-1", "AT:W-1", "2026-05-02T00:00:00Z"),
                cardEvent("card-2-out", EventType.CARD_WITHDRAWN, "DRIVER-1", "interval-2", "VIN-1", "AT:W-1", "2026-05-02T08:00:00Z")
        );
        EventHubEventDto midnightEvidence = vehicleOnlyEvent("pos-midnight", "VIN-1", "AT:W-1", "2026-05-02T00:00:00Z");

        RuntimeDriverVehicleEvidenceAttachmentResult result = service.attachVehicleEvidence(
                "DRIVER-1",
                driverEvents,
                List.of(driverEvents.get(0), driverEvents.get(1), driverEvents.get(2), driverEvents.get(3), midnightEvidence),
                true,
                0
        );

        assertThat(result.vehicleUsageIntervalCount()).isEqualTo(1);
        assertThat(result.attachedVehicleEvidenceEvents()).extracting(EventHubEventDto::externalSourceEventId)
                .containsExactly("pos-midnight");
    }

    private EventHubEventDto cardEvent(
            String externalId,
            EventType eventType,
            String driverKey,
            String intervalId,
            String vehicleKey,
            String registrationKey,
            String occurredAt
    ) {
        EventLifecycle lifecycle = eventType == EventType.CARD_INSERTED ? EventLifecycle.INSERT : EventLifecycle.WITHDRAW;
        return new EventHubEventDto(
                UUID.randomUUID(),
                externalId,
                new DriverRefDto(driverKey, null),
                vehicleRef(vehicleKey, registrationKey),
                OffsetDateTime.parse(occurredAt),
                null,
                OffsetDateTime.parse(occurredAt),
                EventDomain.DRIVER_CARD,
                eventType,
                lifecycle,
                null,
                null,
                null,
                null,
                raw(driverKey, intervalId, vehicleKey, registrationKey),
                false,
                null
        );
    }

    private EventHubEventDto vehicleOnlyEvent(
            String externalId,
            String vehicleKey,
            String registrationKey,
            String occurredAt
    ) {
        return new EventHubEventDto(
                UUID.randomUUID(),
                externalId,
                null,
                vehicleRef(vehicleKey, registrationKey),
                OffsetDateTime.parse(occurredAt),
                null,
                OffsetDateTime.parse(occurredAt),
                EventDomain.POSITION,
                EventType.POSITION_RECORDED,
                EventLifecycle.SNAPSHOT,
                null,
                null,
                null,
                null,
                raw(null, null, vehicleKey, registrationKey),
                false,
                null
        );
    }

    private VehicleRefDto vehicleRef(String vehicleKey, String registrationKey) {
        String[] registrationParts = registrationKey.split(":", 2);
        return new VehicleRefDto(
                "VIN:" + vehicleKey,
                vehicleKey,
                "VR:" + registrationKey,
                new VehicleRegistrationRefDto(registrationParts[0], registrationParts[1])
        );
    }

    private JsonNode raw(String driverKey, String intervalId, String vehicleKey, String registrationKey) {
        ObjectNode root = OBJECT_MAPPER.createObjectNode();
        ObjectNode raw = root.putObject("raw");
        if (driverKey != null) {
            raw.put("driverKey", driverKey);
        }
        if (intervalId != null) {
            raw.put("intervalId", intervalId);
            raw.put("sourceRowId", intervalId);
        }
        raw.put("vehicleKey", vehicleKey);
        raw.put("registrationKey", registrationKey);
        raw.put("sourceKind", "TEST");
        return root;
    }
}
