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

import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
import at.procon.eventhub.processing.dto.UnifiedRuntimeTachographEsperScopeResultDto;
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 at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
import at.procon.eventhub.processing.service.UnifiedRuntimeTachographEsperScopeProcessingService;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.stereotype.Component;

@Component
public class TachographDriverEsperRuntimeEventProcessingProfile implements RuntimeEventProcessingProfile {

    public static final String PROFILE_KEY = "tachograph-driver-esper-v1";

    private final UnifiedRuntimeTachographEsperScopeProcessingService tachographScopeProcessingService;

    public TachographDriverEsperRuntimeEventProcessingProfile(
            UnifiedRuntimeTachographEsperScopeProcessingService tachographScopeProcessingService
    ) {
        this.tachographScopeProcessingService = tachographScopeProcessingService;
    }

    @Override
    public String profileKey() {
        return PROFILE_KEY;
    }

    @Override
    public RuntimeEventPartitioningStrategy defaultPartitioningStrategy() {
        return RuntimeEventPartitioningStrategy.DRIVER;
    }

    @Override
    public String displayName() {
        return "Tachograph Driver Esper Processing";
    }

    @Override
    public String description() {
        return "Runs the shared tachograph driver Esper processing pipeline over Runtime Processing event scopes. "
                + "The profile partitions mixed runtime events by driver before invoking the event-input EPL pipeline.";
    }

    @Override
    public List<RuntimeEventPartitioningStrategy> supportedPartitioningStrategies() {
        return List.of(RuntimeEventPartitioningStrategy.DRIVER);
    }

    @Override
    public Set<String> optionalParameters() {
        return Set.of("significantDrivingMinutes", "minimumRestPeriodMinutes", "attachVehicleOnlyEvents", "vehicleEvidencePaddingMinutes");
    }

    @Override
    public RuntimeEventProcessingResultDto process(RuntimeEventProcessingApiRequest request) {
        UnifiedRuntimeProcessingApiRequest tachographScopeRequest = applyGenericRequest(request.scope(), request.partitioning(), request.parameters());
        UnifiedRuntimeTachographEsperScopeResultDto tachographResult = tachographScopeProcessingService.processScope(tachographScopeRequest);

        Map<String, RuntimeEventProcessingPartitionResultDto> partitionResults = new LinkedHashMap<>();
        tachographResult.driverResults().forEach((driverKey, driverResult) -> partitionResults.put(
                driverKey,
                new RuntimeEventProcessingPartitionResultDto(
                        "DRIVER",
                        driverKey,
                        "UnifiedRuntimeDerivedProjectionResultDto",
                        driverResult,
                        Map.of(
                                "projectionResultType", driverResult.projection() == null ? "NONE" : "TachographEsperDriverProcessingResultDto",
                                "driverSeedEventCount", driverResult.driverSeedEventCount(),
                                "expandedVehicleEventCount", driverResult.expandedVehicleEventCount(),
                                "mergedEventCount", driverResult.mergedEventCount()
                        )
                )
        ));

        return new RuntimeEventProcessingResultDto(
                profileKey(),
                RuntimeEventPartitioningStrategy.DRIVER,
                tachographResult.request(),
                tachographResult.inputEventCount(),
                tachographResult.selectedDriverCount(),
                tachographResult.discoveredVehicleCount(),
                tachographResult.discoveredVehicles(),
                partitionResults,
                tachographResult.notes(),
                tachographResult.warnings()
        );
    }

    private UnifiedRuntimeProcessingApiRequest applyGenericRequest(
            UnifiedRuntimeProcessingApiRequest scope,
            RuntimeEventPartitioningApiRequest partitioning,
            Map<String, Object> parameters
    ) {
        if (scope == null) {
            throw new IllegalArgumentException("scope must not be null for profile " + PROFILE_KEY + ".");
        }
        boolean includeAllDrivers = scope.includeAllDrivers() != null && scope.includeAllDrivers();
        java.util.Set<String> driverKeys = scope.driverKeys();
        boolean includeAllVehicles = scope.includeAllVehicles() != null && scope.includeAllVehicles();
        java.util.Set<String> vehicleKeys = scope.vehicleKeys();

        if (partitioning != null) {
            RuntimeEventPartitioningStrategy strategy = partitioning.strategy() == null
                    ? RuntimeEventPartitioningStrategy.DRIVER
                    : partitioning.strategy();
            if (strategy != RuntimeEventPartitioningStrategy.DRIVER
                    && strategy != RuntimeEventPartitioningStrategy.CUSTOM_PROFILE) {
                throw new IllegalArgumentException("Profile " + PROFILE_KEY
                        + " currently supports DRIVER partitioning only. Requested: " + strategy);
            }
            includeAllDrivers = includeAllDrivers || partitioning.allPartitions() || partitioning.allDrivers();
            if (!partitioning.driverKeys().isEmpty()) {
                driverKeys = partitioning.driverKeys();
            } else if (!partitioning.partitionKeys().isEmpty()) {
                driverKeys = partitioning.partitionKeys();
            }
            includeAllVehicles = includeAllVehicles || partitioning.allVehicles();
            if (!partitioning.vehicleKeys().isEmpty()) {
                vehicleKeys = partitioning.vehicleKeys();
            }
        }

        Integer significantDrivingMinutes = integerParameter(parameters, "significantDrivingMinutes", scope.significantDrivingMinutes());
        Integer minimumRestPeriodMinutes = integerParameter(parameters, "minimumRestPeriodMinutes", scope.minimumRestPeriodMinutes());
        boolean attachVehicleOnlyEvents = booleanParameter(parameters, "attachVehicleOnlyEvents",
                partitioning == null ? scope.expandVehicleEvents() == null || scope.expandVehicleEvents() : partitioning.attachVehicleEvidenceOrDefault());
        Integer vehicleEvidencePaddingMinutes = nonNegativeIntegerParameter(parameters, "vehicleEvidencePaddingMinutes",
                partitioning == null
                        ? scope.vehicleExpansionPaddingMinutes()
                        : partitioning.vehicleEvidencePaddingMinutesOrDefault(scope.vehicleExpansionPaddingMinutes() == null ? 0 : scope.vehicleExpansionPaddingMinutes()));

        return new UnifiedRuntimeProcessingApiRequest(
                scope.sessionId(),
                scope.sessionIds(),
                scope.compositeSessionId(),
                scope.tenantKey(),
                scope.sourceFamilies(),
                scope.eventBackend(),
                scope.driverKey(),
                driverKeys,
                includeAllDrivers,
                vehicleKeys,
                includeAllVehicles,
                scope.driverSourceEntityId(),
                scope.driverCardNation(),
                scope.driverCardNumber(),
                scope.occurredFrom(),
                scope.occurredTo(),
                attachVehicleOnlyEvents,
                vehicleEvidencePaddingMinutes,
                significantDrivingMinutes,
                minimumRestPeriodMinutes
        );
    }

    private boolean booleanParameter(Map<String, Object> parameters, String key, boolean fallback) {
        if (parameters == null || !parameters.containsKey(key)) {
            return fallback;
        }
        Object value = parameters.get(key);
        if (value == null) {
            return fallback;
        }
        if (value instanceof Boolean booleanValue) {
            return booleanValue;
        }
        String text = value.toString();
        if (text == null || text.isBlank()) {
            return fallback;
        }
        return Boolean.parseBoolean(text.trim());
    }

    private Integer nonNegativeIntegerParameter(Map<String, Object> parameters, String key, Integer fallback) {
        if (parameters == null || !parameters.containsKey(key)) {
            return fallback;
        }
        Object value = parameters.get(key);
        if (value == null) {
            return fallback;
        }
        if (value instanceof Number number) {
            return Math.max(0, number.intValue());
        }
        String text = value.toString();
        if (text == null || text.isBlank()) {
            return fallback;
        }
        try {
            return Math.max(0, Integer.parseInt(text.trim()));
        } catch (NumberFormatException ex) {
            throw new IllegalArgumentException("Parameter '" + key + "' must be an integer.", ex);
        }
    }

    private Integer integerParameter(Map<String, Object> parameters, String key, Integer fallback) {
        if (parameters == null || !parameters.containsKey(key)) {
            return fallback;
        }
        Object value = parameters.get(key);
        if (value == null) {
            return fallback;
        }
        if (value instanceof Number number) {
            return Math.max(1, number.intValue());
        }
        String text = value.toString();
        if (text == null || text.isBlank()) {
            return fallback;
        }
        try {
            return Math.max(1, Integer.parseInt(text.trim()));
        } catch (NumberFormatException ex) {
            throw new IllegalArgumentException("Parameter '" + key + "' must be an integer.", ex);
        }
    }
}
