package at.procon.eventhub.processing.eventprocessing.module;

import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDriverPartition;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimePreparedInput;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeProcessingInput;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
import at.procon.eventhub.processing.dto.RuntimeSupportEvidenceNormalizationDebugDto;
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceEvent;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizationResult;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizer;
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SupportEvidenceNormalizationModule implements RuntimeProcessingModule {

    private final RuntimeSupportEvidenceNormalizer supportEvidenceNormalizer;
    private final EventHubProperties properties;

    @Autowired
    public SupportEvidenceNormalizationModule(
            RuntimeSupportEvidenceNormalizer supportEvidenceNormalizer,
            EventHubProperties properties
    ) {
        this.supportEvidenceNormalizer = supportEvidenceNormalizer;
        this.properties = properties;
    }

    /** Compatibility constructor for legacy tests/local registries that still delegate the phase. */
    public SupportEvidenceNormalizationModule() {
        this.supportEvidenceNormalizer = null;
        this.properties = null;
    }

    @Override
    public String moduleKey() {
        return DriverWorkingTimeModuleKeys.SUPPORT_EVIDENCE_NORMALIZATION;
    }

    @Override
    public RuntimeProcessingModuleDescriptorDto descriptor() {
        return new RuntimeProcessingModuleDescriptorDto(
                moduleKey(),
                "Support evidence normalization",
                "Normalizes mixed-source support evidence into typed per-driver working-time inputs for the common processing core.",
                "JAVA",
                Set.of("Map<String, DriverWorkingTimePreparedInput>")
        );
    }

    @Override
    public RuntimeProcessingModuleResult execute(RuntimeProcessingModuleContext context) {
        if (supportEvidenceNormalizer == null || properties == null) {
            return delegatedPlaceholder();
        }
        UnifiedRuntimeProcessingApiRequest scopeRequest = scopeRequest(context);
        UnifiedRuntimeProcessingRequest request = scopeRequest.toRuntimeRequest();
        Map<String, DriverWorkingTimeDriverPartition> partitions = partitions(context);
        List<DriverWorkingTimeActivityInterval> activityIntervals = activityIntervals(context);

        LinkedHashMap<String, DriverWorkingTimePreparedInput> preparedInputs = new LinkedHashMap<>();
        List<String> warnings = new ArrayList<>();
        for (Map.Entry<String, DriverWorkingTimeDriverPartition> entry : partitions.entrySet()) {
            String driverKey = entry.getKey();
            DriverWorkingTimeDriverPartition partition = entry.getValue();
            List<DriverWorkingTimeActivityInterval> driverActivityIntervals = activityIntervals.stream()
                    .filter(interval -> Objects.equals(driverKey, interval.driverKey()))
                    .sorted(Comparator.comparing(DriverWorkingTimeActivityInterval::startedAt, Comparator.nullsLast(Comparator.naturalOrder()))
                            .thenComparing(DriverWorkingTimeActivityInterval::endedAt, Comparator.nullsLast(Comparator.naturalOrder())))
                    .toList();

            RuntimeSupportEvidenceNormalizationResult normalizationResult =
                    supportEvidenceNormalizer.normalizeForTachographDriver(driverKey, partition.mergedEvents());
            List<RuntimeSupportEvidenceEvent> supportEvidenceEvents = normalizationResult.normalizedEvents().stream()
                    .map(event -> supportEvidenceNormalizer.toSupportEvidenceEvent(driverKey, event))
                    .filter(Objects::nonNull)
                    .filter(event -> event.occurredAt() != null)
                    .toList();
            RuntimeSupportEvidenceNormalizationDebugDto normalizationDebug =
                    new RuntimeSupportEvidenceNormalizationDebugDto(
                            normalizationResult.inputEventCount(),
                            normalizationResult.normalizedSupportEvidenceEventCount(),
                            normalizationResult.unchangedEventCount(),
                            normalizationResult.notes()
                    );

            List<String> notes = new ArrayList<>(partition.notes());
            notes.addAll(normalizationResult.notes());
            notes.add("Support evidence normalization produced " + supportEvidenceEvents.size()
                    + " typed support evidence event(s) for driver " + driverKey + ".");
            List<String> partitionWarnings = new ArrayList<>(partition.warnings());
            warnings.addAll(partitionWarnings);

            DriverWorkingTimeDriverPartition normalizedPartition = partition.withSupportEvidence(
                    supportEvidenceEvents,
                    normalizationDebug,
                    notes,
                    partitionWarnings
            );
            DriverWorkingTimeProcessingInput processingInput = new DriverWorkingTimeProcessingInput(
                    runtimeSessionId(request),
                    driverKey,
                    resolveSourceKind(normalizedPartition, driverActivityIntervals),
                    resolveLoadedFrom(normalizedPartition, driverActivityIntervals, supportEvidenceEvents),
                    resolveLoadedTo(normalizedPartition, driverActivityIntervals, supportEvidenceEvents),
                    scopeRequest.occurredFrom(),
                    scopeRequest.occurredTo(),
                    scopeRequest.significantDrivingMinutes() == null
                            ? properties.getTachographFileSession().getProcessing().getSignificantDrivingMinutes()
                            : scopeRequest.significantDrivingMinutes(),
                    scopeRequest.minimumRestPeriodMinutes() == null
                            ? properties.getTachographFileSession().getProcessing().getMinimumRestPeriodMinutes()
                            : scopeRequest.minimumRestPeriodMinutes(),
                    driverActivityIntervals,
                    normalizedPartition.vehicleUsageIntervals(),
                    supportEvidenceEvents,
                    notes
            );
            preparedInputs.put(driverKey, new DriverWorkingTimePreparedInput(
                    driverKey,
                    normalizedPartition,
                    processingInput
            ));
        }

        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.put("selectedDriverCount", preparedInputs.size());
        metadata.put("typedSupportEvidenceEventCount", preparedInputs.values().stream()
                .mapToInt(value -> value.processingInput().supportEvidenceEvents().size())
                .sum());
        return new RuntimeProcessingModuleResult(
                moduleKey(),
                RuntimeProcessingModuleStatus.SUCCESS,
                Map.copyOf(preparedInputs),
                metadata,
                List.copyOf(warnings)
        );
    }

    private RuntimeProcessingModuleResult delegatedPlaceholder() {
        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.put("executionModel", "delegated");
        metadata.put("delegatedTo", DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS);
        metadata.put("note", "This logical module is executed inside the legacy derived projections adapter.");
        return new RuntimeProcessingModuleResult(
                moduleKey(),
                RuntimeProcessingModuleStatus.SUCCESS,
                Map.of(),
                metadata,
                List.of()
        );
    }

    private UnifiedRuntimeProcessingApiRequest scopeRequest(RuntimeProcessingModuleContext context) {
        Object value = context.attributes().get("runtimeScopeApiRequest");
        if (value instanceof UnifiedRuntimeProcessingApiRequest request) {
            return request;
        }
        return context.request().sourceSelection();
    }

    @SuppressWarnings("unchecked")
    private Map<String, DriverWorkingTimeDriverPartition> partitions(RuntimeProcessingModuleContext context) {
        RuntimeProcessingModuleResult result = context.previousResults().get(DriverWorkingTimeModuleKeys.VEHICLE_EVIDENCE_ATTACHMENT);
        if (result == null || !(result.output() instanceof Map<?, ?> map)) {
            return Map.of();
        }
        return (Map<String, DriverWorkingTimeDriverPartition>) map;
    }

    @SuppressWarnings("unchecked")
    private List<DriverWorkingTimeActivityInterval> activityIntervals(RuntimeProcessingModuleContext context) {
        RuntimeProcessingModuleResult result = context.previousResults().get(DriverWorkingTimeModuleKeys.EVENT_TO_ACTIVITY_INTERVALS);
        if (result == null || !(result.output() instanceof List<?> list)) {
            return List.of();
        }
        return (List<DriverWorkingTimeActivityInterval>) list;
    }

    private UUID runtimeSessionId(UnifiedRuntimeProcessingRequest request) {
        if (request.compositeSessionId() != null || request.sessionIds().size() > 1) {
            return null;
        }
        return request.sessionIds().size() == 1 ? request.sessionIds().get(0) : request.sessionId();
    }

    private String resolveSourceKind(
            DriverWorkingTimeDriverPartition partition,
            List<DriverWorkingTimeActivityInterval> driverActivityIntervals
    ) {
        for (DriverWorkingTimeVehicleUsageInterval interval : partition.vehicleUsageIntervals()) {
            if (interval.sourceKind() != null && !interval.sourceKind().isBlank()) {
                return interval.sourceKind();
            }
        }
        for (DriverWorkingTimeActivityInterval interval : driverActivityIntervals) {
            if (interval.sourceKind() != null && !interval.sourceKind().isBlank()) {
                return interval.sourceKind();
            }
        }
        return "UNIFIED_RUNTIME";
    }

    private OffsetDateTime resolveLoadedFrom(
            DriverWorkingTimeDriverPartition partition,
            List<DriverWorkingTimeActivityInterval> driverActivityIntervals,
            List<RuntimeSupportEvidenceEvent> supportEvidenceEvents
    ) {
        return earliest(
                driverActivityIntervals.stream().map(DriverWorkingTimeActivityInterval::startedAt).toList(),
                partition.vehicleUsageIntervals().stream().map(DriverWorkingTimeVehicleUsageInterval::startedAt).toList(),
                supportEvidenceEvents.stream().map(RuntimeSupportEvidenceEvent::occurredAt).toList()
        );
    }

    private OffsetDateTime resolveLoadedTo(
            DriverWorkingTimeDriverPartition partition,
            List<DriverWorkingTimeActivityInterval> driverActivityIntervals,
            List<RuntimeSupportEvidenceEvent> supportEvidenceEvents
    ) {
        return latest(
                driverActivityIntervals.stream().map(DriverWorkingTimeActivityInterval::endedAt).toList(),
                partition.vehicleUsageIntervals().stream().map(DriverWorkingTimeVehicleUsageInterval::endedAt).toList(),
                supportEvidenceEvents.stream().map(RuntimeSupportEvidenceEvent::occurredAt).toList()
        );
    }

    @SafeVarargs
    private final OffsetDateTime earliest(List<OffsetDateTime>... values) {
        OffsetDateTime earliest = null;
        for (List<OffsetDateTime> candidates : values) {
            for (OffsetDateTime candidate : candidates == null ? List.<OffsetDateTime>of() : candidates) {
                if (candidate != null && (earliest == null || candidate.isBefore(earliest))) {
                    earliest = candidate;
                }
            }
        }
        return earliest;
    }

    @SafeVarargs
    private final OffsetDateTime latest(List<OffsetDateTime>... values) {
        OffsetDateTime latest = null;
        for (List<OffsetDateTime> candidates : values) {
            for (OffsetDateTime candidate : candidates == null ? List.<OffsetDateTime>of() : candidates) {
                if (candidate != null && (latest == null || candidate.isAfter(latest))) {
                    latest = candidate;
                }
            }
        }
        return latest;
    }
}
