package click.kamil.springstatemachineexporter.analysis.enricher;

import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.EntryPointKeys;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.analysis.service.CallGraphEngine;
import click.kamil.springstatemachineexporter.analysis.service.GenericEventDetector;
import click.kamil.springstatemachineexporter.analysis.service.JdtCallGraphEngine;
import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
import click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry;
import click.kamil.springstatemachineexporter.analysis.spring.SpringContextScanner;
import click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.CompilationUnit;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Scopes REST entry points to a machine using call-graph reachability and trigger type evidence,
 * not URL path segment guessing.
 */
public final class EntryPointScopeResolver {

    private static final BeanResolutionEngine ROUTING = new HeuristicBeanResolutionEngine();
    private static final String TRIGGERS_CACHE = "entryPointScope.triggers";

    public enum Affinity {
        FOR_MACHINE,
        AGAINST_MACHINE,
        UNKNOWN
    }

    private EntryPointScopeResolver() {
    }

    public static List<EntryPoint> scopeFromCallChains(List<CallChain> chains, List<EntryPoint> allEntryPoints) {
        Map<String, EntryPoint> scoped = new LinkedHashMap<>();
        if (chains != null) {
            for (CallChain chain : chains) {
                EntryPoint entryPoint = chain.getEntryPoint();
                if (entryPoint != null) {
                    scoped.putIfAbsent(EntryPointKeys.of(entryPoint), entryPoint);
                }
            }
        }
        if (allEntryPoints != null) {
            for (EntryPoint entryPoint : allEntryPoints) {
                if (hasPathVariablePlaceholder(entryPoint)) {
                    scoped.putIfAbsent(EntryPointKeys.of(entryPoint), entryPoint);
                }
            }
        }
        return new ArrayList<>(scoped.values());
    }

    public static Affinity resolveAffinity(EntryPoint entryPoint, String machineName, CodebaseContext context) {
        if (entryPoint == null || machineName == null) {
            return Affinity.UNKNOWN;
        }
        if (entryPoint.getClassName() == null || entryPoint.getMethodName() == null) {
            return Affinity.UNKNOWN;
        }
        if (context == null || context.getCompilationUnits().isEmpty()) {
            return Affinity.UNKNOWN;
        }

        CallGraphEngine engine = createCallGraphEngine(context);
        List<CallChain> chains = engine.findChains(List.of(entryPoint), getOrCollectTriggers(context));
        if (chains.isEmpty()) {
            return Affinity.UNKNOWN;
        }

        boolean matched = false;
        boolean mismatched = false;
        for (CallChain chain : chains) {
            TriggerPoint trigger = chain.getTriggerPoint();
            if (trigger == null || !hasTypeEvidence(trigger)) {
                continue;
            }
            if (ROUTING.isRoutedToCorrectMachine(chain, machineName, context)) {
                matched = true;
            } else {
                mismatched = true;
            }
        }

        if (matched) {
            return Affinity.FOR_MACHINE;
        }
        if (mismatched) {
            return Affinity.AGAINST_MACHINE;
        }
        return Affinity.UNKNOWN;
    }

    public static boolean hasPathVariablePlaceholder(EntryPoint entryPoint) {
        if (entryPoint == null) {
            return false;
        }
        String name = entryPoint.getName();
        if (name != null && name.contains("{")) {
            return true;
        }
        Map<String, String> metadata = entryPoint.getMetadata();
        if (metadata != null) {
            String path = metadata.get("path");
            return path != null && path.contains("{");
        }
        return false;
    }

    private static boolean hasTypeEvidence(TriggerPoint trigger) {
        if (trigger.getEventTypeFqn() != null || trigger.getStateTypeFqn() != null) {
            return true;
        }
        return MachineEnumCanonicalizer.classifyTriggerEvent(trigger.getEvent())
                == MachineEnumCanonicalizer.TriggerEventKind.CANONICAL_ENUM;
    }

    @SuppressWarnings("unchecked")
    private static List<TriggerPoint> getOrCollectTriggers(CodebaseContext context) {
        Object cached = context.getCache().get(TRIGGERS_CACHE);
        if (cached instanceof List<?> list) {
            return (List<TriggerPoint>) list;
        }
        List<TriggerPoint> triggers = new ArrayList<>();
        GenericEventDetector detector =
                new GenericEventDetector(context, context.getConstantResolver(), context.getLibraryHints());
        for (CompilationUnit cu : context.getCompilationUnits()) {
            triggers.addAll(detector.detect(cu));
        }
        context.getCache().put(TRIGGERS_CACHE, triggers);
        return triggers;
    }

    private static CallGraphEngine createCallGraphEngine(CodebaseContext context) {
        SpringBeanRegistry registry = new SpringBeanRegistry();
        SpringContextScanner scanner = new SpringContextScanner(registry);
        for (CompilationUnit cu : context.getCompilationUnits()) {
            cu.accept(scanner);
        }
        SpringDependencyResolver dependencyResolver = new SpringDependencyResolver(registry);
        InjectionPointAnalyzer injectionAnalyzer = new InjectionPointAnalyzer(dependencyResolver);
        return new JdtCallGraphEngine(context, injectionAnalyzer);
    }
}
