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

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;

@Component
public class RuntimeProcessingPlanRegistry {

    private final Map<String, RuntimeProcessingPlan> plansByKey;
    private final Map<String, RuntimeProcessingPlan> plansByKeyAndAlias;

    public RuntimeProcessingPlanRegistry(List<RuntimeProcessingPlan> plans) {
        LinkedHashMap<String, RuntimeProcessingPlan> byKey = new LinkedHashMap<>();
        LinkedHashMap<String, RuntimeProcessingPlan> byKeyAndAlias = new LinkedHashMap<>();
        for (RuntimeProcessingPlan plan : plans == null ? List.<RuntimeProcessingPlan>of() : plans) {
            String key = normalize(plan.processingPlanKey());
            if (key == null) {
                throw new IllegalStateException("Runtime processing plan returned a blank processingPlanKey: " + plan.getClass().getName());
            }
            RuntimeProcessingPlan previous = byKey.putIfAbsent(key, plan);
            if (previous != null) {
                throw new IllegalStateException("Duplicate runtime processingPlanKey: " + key);
            }
            putAlias(byKeyAndAlias, key, plan);
            for (String alias : plan.aliases()) {
                String normalizedAlias = normalize(alias);
                if (normalizedAlias != null) {
                    putAlias(byKeyAndAlias, normalizedAlias, plan);
                }
            }
        }
        this.plansByKey = Collections.unmodifiableMap(byKey);
        this.plansByKeyAndAlias = Collections.unmodifiableMap(byKeyAndAlias);
    }

    public RuntimeProcessingPlan require(String processingPlanKey) {
        String normalized = normalize(processingPlanKey);
        RuntimeProcessingPlan plan = normalized == null ? null : plansByKeyAndAlias.get(normalized);
        if (plan == null) {
            throw new IllegalArgumentException("Unknown runtime processingPlanKey: " + processingPlanKey
                    + ". Available plans: " + plansByKey.keySet());
        }
        return plan;
    }

    public Map<String, RuntimeProcessingPlan> plansByKey() {
        return plansByKey;
    }

    public List<RuntimeProcessingPlanDescriptorDto> planDescriptors() {
        return plansByKey.values().stream()
                .map(RuntimeProcessingPlanDescriptorDto::from)
                .toList();
    }

    private void putAlias(Map<String, RuntimeProcessingPlan> target, String key, RuntimeProcessingPlan plan) {
        RuntimeProcessingPlan previous = target.putIfAbsent(key, plan);
        if (previous != null && previous != plan) {
            throw new IllegalStateException("Duplicate runtime processing plan alias/key: " + key);
        }
    }

    private String normalize(String key) {
        if (key == null || key.isBlank()) {
            return null;
        }
        return key.trim();
    }
}
