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

import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimePreparedInput;
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
import at.procon.eventhub.processing.dto.UnifiedRuntimeDriverWorkingTimeScopeResultDto;
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
import at.procon.eventhub.processing.service.RuntimeDriverWorkingTimeScopeProcessingService;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeProcessingCore;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class DriverWorkingTimeDerivedProjectionsModule implements RuntimeProcessingModule {

    private final DriverWorkingTimeProcessingCore workingTimeProcessingCore;
    private final RuntimeDriverWorkingTimeScopeProcessingService legacyScopeProcessingService;

    @Autowired
    public DriverWorkingTimeDerivedProjectionsModule(
            DriverWorkingTimeProcessingCore workingTimeProcessingCore
    ) {
        this.workingTimeProcessingCore = workingTimeProcessingCore;
        this.legacyScopeProcessingService = null;
    }

    public DriverWorkingTimeDerivedProjectionsModule(
            RuntimeDriverWorkingTimeScopeProcessingService scopeProcessingService
    ) {
        this.workingTimeProcessingCore = null;
        this.legacyScopeProcessingService = scopeProcessingService;
    }

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

    @Override
    public RuntimeProcessingModuleDescriptorDto descriptor() {
        return new RuntimeProcessingModuleDescriptorDto(
                moduleKey(),
                "Driving-derived projections",
                "Executes the shared driver working-time core from typed per-driver module outputs for driving interruptions, rest candidates, card-absence coverage, overnight candidates, and trip candidates.",
                "ESPER+JAVA",
                Set.of("UnifiedRuntimeDriverWorkingTimeScopeResultDto")
        );
    }

    @Override
    public RuntimeProcessingModuleResult execute(RuntimeProcessingModuleContext context) {
        if (legacyScopeProcessingService != null) {
            return executeLegacy(context);
        }
        UnifiedRuntimeEventBundle broadBundle = runtimeEventBundle(context);
        UnifiedRuntimeProcessingApiRequest scopeRequest = scopeRequest(context);
        Map<String, DriverWorkingTimePreparedInput> preparedInputs = preparedInputs(context);

        LinkedHashMap<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults = new LinkedHashMap<>();
        List<String> warnings = new ArrayList<>();
        for (Map.Entry<String, DriverWorkingTimePreparedInput> entry : preparedInputs.entrySet()) {
            DriverWorkingTimePreparedInput preparedInput = entry.getValue();
            DriverWorkingTimeProcessingResultDto projection =
                    workingTimeProcessingCore.process(preparedInput.processingInput());
            warnings.addAll(preparedInput.partition().warnings());
            UnifiedRuntimeProcessingRequest driverRequest = broadBundle.request().withDriverKey(preparedInput.driverKey());
            driverResults.put(preparedInput.driverKey(), new UnifiedRuntimeDerivedProjectionResultDto(
                    driverRequest,
                    preparedInput.partition().driverSeedEvents().size(),
                    preparedInput.partition().discoveredVehicles().size(),
                    preparedInput.partition().attachedVehicleEvidenceEvents().size(),
                    preparedInput.partition().mergedEvents().size(),
                    preparedInput.partition().discoveredVehicles(),
                    projection,
                    projection.notes(),
                    preparedInput.partition().supportEvidenceNormalization(),
                    preparedInput.partition().partitionDebug()
            ));
        }

        List<String> notes = new ArrayList<>(broadBundle.notes());
        notes.add("Runtime driver working-time processing used module-to-module dataflow for event assembly, activity intervalization, vehicle-usage intervalization, evidence attachment, support evidence normalization, and final derived projections.");
        notes.add("Selected driver partitions: " + driverResults.size() + ".");

        UnifiedRuntimeDriverWorkingTimeScopeResultDto result = new UnifiedRuntimeDriverWorkingTimeScopeResultDto(
                broadBundle.request(),
                broadBundle.mergedEvents().size(),
                driverResults.size(),
                broadBundle.discoveredVehicles().size(),
                broadBundle.discoveredVehicles(),
                driverResults,
                partitionDebug(driverResults),
                notes,
                List.copyOf(warnings)
        );
        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.put("inputEventCount", result.inputEventCount());
        metadata.put("selectedDriverCount", result.selectedDriverCount());
        metadata.put("discoveredVehicleCount", result.discoveredVehicleCount());
        metadata.put("driverResultCount", result.driverResults().size());
        return new RuntimeProcessingModuleResult(
                moduleKey(),
                RuntimeProcessingModuleStatus.SUCCESS,
                result,
                metadata,
                result.warnings()
        );
    }

    private RuntimeProcessingModuleResult executeLegacy(RuntimeProcessingModuleContext context) {
        UnifiedRuntimeProcessingApiRequest scopeRequest = scopeRequest(context);
        boolean includePartitionDebug = booleanAttribute(context, "includePartitionDebug", false);
        UnifiedRuntimeDriverWorkingTimeScopeResultDto result = legacyScopeProcessingService.processScope(
                scopeRequest,
                includePartitionDebug
        );
        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.put("inputEventCount", result.inputEventCount());
        metadata.put("selectedDriverCount", result.selectedDriverCount());
        metadata.put("discoveredVehicleCount", result.discoveredVehicleCount());
        metadata.put("driverResultCount", result.driverResults().size());
        return new RuntimeProcessingModuleResult(
                moduleKey(),
                RuntimeProcessingModuleStatus.SUCCESS,
                result,
                metadata,
                result.warnings()
        );
    }

    private UnifiedRuntimeEventBundle runtimeEventBundle(RuntimeProcessingModuleContext context) {
        RuntimeProcessingModuleResult result = context.previousResults().get(DriverWorkingTimeModuleKeys.RUNTIME_EVENT_ASSEMBLY);
        if (result != null && result.output() instanceof UnifiedRuntimeEventBundle bundle) {
            return bundle;
        }
        throw new IllegalStateException("Module " + moduleKey()
                + " requires previous result " + DriverWorkingTimeModuleKeys.RUNTIME_EVENT_ASSEMBLY + ".");
    }

    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, DriverWorkingTimePreparedInput> preparedInputs(RuntimeProcessingModuleContext context) {
        RuntimeProcessingModuleResult result =
                context.previousResults().get(DriverWorkingTimeModuleKeys.SUPPORT_EVIDENCE_NORMALIZATION);
        if (result == null || !(result.output() instanceof Map<?, ?> map)) {
            return Map.of();
        }
        return (Map<String, DriverWorkingTimePreparedInput>) map;
    }

    private Map<String, at.procon.eventhub.processing.dto.RuntimeDriverPartitionDebugDto> partitionDebug(
            Map<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults
    ) {
        LinkedHashMap<String, at.procon.eventhub.processing.dto.RuntimeDriverPartitionDebugDto> debugByDriver =
                new LinkedHashMap<>();
        driverResults.forEach((driverKey, result) -> {
            if (result.partitionDebug() != null) {
                debugByDriver.put(driverKey, result.partitionDebug());
            }
        });
        return debugByDriver;
    }

    private boolean booleanAttribute(RuntimeProcessingModuleContext context, String key, boolean fallback) {
        Object value = context.attributes().get(key);
        if (value instanceof Boolean booleanValue) {
            return booleanValue;
        }
        return fallback;
    }
}
