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

import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.springframework.stereotype.Component;

@Component
public class DriverVehicleUsageMergeModule implements RuntimeProcessingModule {

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

    @Override
    public RuntimeProcessingModuleDescriptorDto descriptor() {
        return new RuntimeProcessingModuleDescriptorDto(
                moduleKey(),
                "Vehicle usage merge",
                "Merges adjacent or continuous same-driver/same-vehicle usage intervals, including 23:59:59 to 00:00:00 continuations.",
                "JAVA",
                Set.of("DriverWorkingTimeVehicleUsageInterval")
        );
    }

    @Override
    public RuntimeProcessingModuleResult execute(RuntimeProcessingModuleContext context) {
        Object output = context.previousResults().get(DriverWorkingTimeModuleKeys.EVENT_TO_VEHICLE_USAGE_INTERVALS) == null
                ? null
                : context.previousResults().get(DriverWorkingTimeModuleKeys.EVENT_TO_VEHICLE_USAGE_INTERVALS).output();
        List<DriverWorkingTimeVehicleUsageInterval> intervals = castIntervals(output);
        List<DriverWorkingTimeVehicleUsageInterval> merged = merge(intervals);
        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.put("inputIntervalCount", intervals.size());
        metadata.put("mergedIntervalCount", merged.size());
        return new RuntimeProcessingModuleResult(
                moduleKey(),
                RuntimeProcessingModuleStatus.SUCCESS,
                merged,
                metadata,
                List.of()
        );
    }

    @SuppressWarnings("unchecked")
    private List<DriverWorkingTimeVehicleUsageInterval> castIntervals(Object output) {
        return output instanceof List<?> list
                ? (List<DriverWorkingTimeVehicleUsageInterval>) list
                : List.of();
    }

    private List<DriverWorkingTimeVehicleUsageInterval> merge(List<DriverWorkingTimeVehicleUsageInterval> intervals) {
        if (intervals == null || intervals.isEmpty()) {
            return List.of();
        }
        List<DriverWorkingTimeVehicleUsageInterval> sorted = intervals.stream()
                .sorted(Comparator.comparing(DriverWorkingTimeVehicleUsageInterval::startedAt, Comparator.nullsLast(Comparator.naturalOrder()))
                        .thenComparing(DriverWorkingTimeVehicleUsageInterval::endedAt, Comparator.nullsLast(Comparator.naturalOrder()))
                        .thenComparing(DriverWorkingTimeVehicleUsageInterval::intervalId, Comparator.nullsLast(String::compareTo)))
                .toList();
        List<DriverWorkingTimeVehicleUsageInterval> merged = new ArrayList<>();
        for (DriverWorkingTimeVehicleUsageInterval next : sorted) {
            if (merged.isEmpty()) {
                merged.add(next);
                continue;
            }
            DriverWorkingTimeVehicleUsageInterval current = merged.get(merged.size() - 1);
            if (canMerge(current, next)) {
                merged.set(merged.size() - 1, merge(current, next));
            } else {
                merged.add(next);
            }
        }
        return List.copyOf(merged);
    }

    private boolean canMerge(
            DriverWorkingTimeVehicleUsageInterval left,
            DriverWorkingTimeVehicleUsageInterval right
    ) {
        if (left == null || right == null || left.endedAt() == null || right.startedAt() == null) {
            return false;
        }
        return Objects.equals(left.driverKey(), right.driverKey())
                && Objects.equals(left.registrationKey(), right.registrationKey())
                && Objects.equals(left.vehicleKey(), right.vehicleKey())
                && !right.startedAt().isAfter(left.endedAt().plusSeconds(1));
    }

    private DriverWorkingTimeVehicleUsageInterval merge(
            DriverWorkingTimeVehicleUsageInterval left,
            DriverWorkingTimeVehicleUsageInterval right
    ) {
        LinkedHashSet<String> sourceIntervalIds = new LinkedHashSet<>(left.sourceIntervalIds());
        sourceIntervalIds.addAll(right.sourceIntervalIds());
        OffsetDateTime mergedEnd = left.endedAt();
        if (right.endedAt() != null && (mergedEnd == null || right.endedAt().isAfter(mergedEnd))) {
            mergedEnd = right.endedAt();
        }
        return new DriverWorkingTimeVehicleUsageInterval(
                left.sessionId(),
                left.driverKey(),
                left.intervalId(),
                left.firstSourceIntervalId(),
                right.lastSourceIntervalId() == null ? left.lastSourceIntervalId() : right.lastSourceIntervalId(),
                left.startedAt(),
                mergedEnd,
                left.startedAtEpochSecond(),
                mergedEnd == null ? null : mergedEnd.toEpochSecond(),
                mergedEnd == null ? left.durationSeconds() : mergedEnd.toEpochSecond() - left.startedAtEpochSecond(),
                left.odometerBeginKm(),
                right.odometerEndKm() == null ? left.odometerEndKm() : right.odometerEndKm(),
                left.registrationKey(),
                left.vehicleKey(),
                left.sourceKind(),
                List.copyOf(sourceIntervalIds)
        );
    }
}
