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

import at.procon.eventhub.processing.dto.RuntimeDriverPartitionDebugDto;
import at.procon.eventhub.processing.dto.RuntimeSupportEvidenceNormalizationDebugDto;
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
import at.procon.eventhub.processing.eventprocessing.RuntimeEventProcessingService;
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventPartitioningApiRequest;
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingApiRequest;
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.stereotype.Service;

@Service
public class RuntimeMixedSourceEvidenceValidationService {

    private final RuntimeEventProcessingService runtimeEventProcessingService;

    public RuntimeMixedSourceEvidenceValidationService(RuntimeEventProcessingService runtimeEventProcessingService) {
        this.runtimeEventProcessingService = runtimeEventProcessingService;
    }

    public RuntimeMixedSourceEvidenceValidationResultDto validate(RuntimeMixedSourceEvidenceValidationApiRequest request) {
        RuntimeEventProcessingApiRequest debugRequest = withDebugEnabled(request.processingRequest());
        RuntimeEventProcessingResultDto runtimeResult = runtimeEventProcessingService.process(debugRequest);

        LinkedHashMap<String, RuntimeMixedSourceEvidencePartitionValidationDto> partitionResults = new LinkedHashMap<>();
        List<String> warnings = new ArrayList<>(runtimeResult.warnings());
        List<String> notes = new ArrayList<>(runtimeResult.notes());
        notes.add("Mixed-source evidence validation executed runtime event processing with partition debug enabled.");
        notes.add("Validation checks vehicle-only evidence attachment and support-evidence normalization counts; it does not compare legal tachograph result semantics.");

        int totalAttached = 0;
        int totalIgnored = 0;
        int totalNormalized = 0;
        int totalSupportGeo = 0;

        for (Map.Entry<String, RuntimeEventProcessingPartitionResultDto> entry : runtimeResult.partitionResults().entrySet()) {
            RuntimeMixedSourceEvidencePartitionValidationDto partition = partitionValidation(
                    entry.getKey(),
                    entry.getValue(),
                    request.failWhenPartitionDebugMissingOrDefault()
            );
            partitionResults.put(entry.getKey(), partition);
            totalAttached += partition.attachedVehicleEvidenceEventCount();
            totalIgnored += partition.ignoredVehicleEvidenceEventCount();
            totalNormalized += partition.normalizedSupportEvidenceEventCount();
            totalSupportGeo += partition.supportGeoEventCount();
            warnings.addAll(partition.warnings());
        }

        Set<String> missingPartitions = new LinkedHashSet<>(request.expectedPartitionKeys());
        missingPartitions.removeAll(partitionResults.keySet());
        for (String missingPartition : missingPartitions) {
            warnings.add("Expected partition " + missingPartition + " was not returned by runtime event processing.");
        }
        if (totalAttached < request.minimumAttachedVehicleEvidenceEventsOrDefault()) {
            warnings.add("Attached vehicle evidence count " + totalAttached + " is below expected minimum "
                    + request.minimumAttachedVehicleEvidenceEventsOrDefault() + ".");
        }
        if (totalNormalized < request.minimumNormalizedSupportEvidenceEventsOrDefault()) {
            warnings.add("Normalized support evidence count " + totalNormalized + " is below expected minimum "
                    + request.minimumNormalizedSupportEvidenceEventsOrDefault() + ".");
        }

        boolean failed = !missingPartitions.isEmpty()
                || totalAttached < request.minimumAttachedVehicleEvidenceEventsOrDefault()
                || totalNormalized < request.minimumNormalizedSupportEvidenceEventsOrDefault()
                || partitionResults.values().stream().anyMatch(partition -> "FAILED".equals(partition.status()));

        return new RuntimeMixedSourceEvidenceValidationResultDto(
                runtimeResult.profileKey(),
                failed ? "FAILED" : "PASSED",
                partitionResults.size(),
                totalAttached,
                totalIgnored,
                totalNormalized,
                totalSupportGeo,
                partitionResults,
                notes,
                warnings
        );
    }

    private RuntimeMixedSourceEvidencePartitionValidationDto partitionValidation(
            String partitionKey,
            RuntimeEventProcessingPartitionResultDto partitionResult,
            boolean failWhenPartitionDebugMissing
    ) {
        UnifiedRuntimeDerivedProjectionResultDto result = partitionResult.result() instanceof UnifiedRuntimeDerivedProjectionResultDto derived
                ? derived
                : null;
        RuntimeDriverPartitionDebugDto debug = result == null ? null : result.partitionDebug();
        RuntimeSupportEvidenceNormalizationDebugDto normalization = result == null ? null : result.supportEvidenceNormalization();

        List<String> notes = new ArrayList<>();
        List<String> warnings = new ArrayList<>();
        if (result == null) {
            warnings.add("Partition " + partitionKey + " did not return UnifiedRuntimeDerivedProjectionResultDto; resultType="
                    + partitionResult.resultType() + ".");
        }
        if (debug == null) {
            String message = "Partition " + partitionKey + " has no partition debug metadata.";
            if (failWhenPartitionDebugMissing) {
                warnings.add(message);
            } else {
                notes.add(message);
            }
        } else {
            notes.addAll(debug.notes());
            warnings.addAll(debug.warnings());
        }
        if (normalization == null) {
            warnings.add("Partition " + partitionKey + " has no support-evidence normalization metadata.");
        } else {
            notes.addAll(normalization.notes());
        }

        int directDriverEventCount = debug == null ? (result == null ? 0 : result.driverSeedEventCount()) : debug.directDriverEventCount();
        int attached = debug == null ? (result == null ? 0 : result.expandedVehicleEventCount()) : debug.attachedVehicleEvidenceEventCount();
        int ignored = debug == null ? 0 : debug.ignoredVehicleEvidenceEventCount();
        int merged = debug == null ? (result == null ? 0 : result.mergedEventCount()) : debug.mergedEventCount();
        int normalized = normalization == null ? 0 : normalization.normalizedSupportEvidenceEventCount();
        int unchanged = normalization == null ? 0 : normalization.unchangedEventCount();
        int supportGeo = result == null || result.projection() == null ? 0 : result.projection().supportGeoEventCount();

        String status = warnings.isEmpty() || (!failWhenPartitionDebugMissing && debug == null && warnings.size() == 1)
                ? "PASSED"
                : "FAILED";
        return new RuntimeMixedSourceEvidencePartitionValidationDto(
                partitionKey,
                status,
                directDriverEventCount,
                attached,
                ignored,
                merged,
                normalized,
                unchanged,
                supportGeo,
                debug != null,
                notes,
                warnings
        );
    }

    private RuntimeEventProcessingApiRequest withDebugEnabled(RuntimeEventProcessingApiRequest request) {
        RuntimeEventPartitioningApiRequest partitioning = request.partitioning();
        RuntimeEventPartitioningApiRequest debugPartitioning = new RuntimeEventPartitioningApiRequest(
                partitioning.strategy(),
                partitioning.partitionKeys(),
                partitioning.includeAllPartitions(),
                partitioning.driverKeys(),
                partitioning.includeAllDrivers(),
                partitioning.vehicleKeys(),
                partitioning.includeAllVehicles(),
                partitioning.attachVehicleEvidence(),
                partitioning.vehicleEvidencePaddingMinutes(),
                true
        );
        Map<String, Object> parameters = new LinkedHashMap<>(request.parameters());
        parameters.put("includePartitionDebug", true);
        return new RuntimeEventProcessingApiRequest(
                request.profileKey(),
                request.scope(),
                debugPartitioning,
                parameters
        );
    }
}
