package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
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 lombok.extern.slf4j.Slf4j;
import org.eclipse.jdt.core.dom.CompilationUnit;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
    private final CodebaseContext context;
    private final Path rootDir;
    private final ConstantResolver constantResolver;
    private final GenericEventDetector eventDetector;
    private final SpringMvcDetector mvcDetector;
    private final MessagingDetector messagingDetector;
    private final CallGraphEngine callGraphEngine;
    private final LifecycleDetector lifecycleDetector;
    private final InterceptorDetector interceptorDetector;
    private final SpringComponentDetector componentDetector;
    private List<TriggerPoint> cachedTriggers;
    private List<EntryPoint> cachedEntryPoints;

    public JdtIntelligenceProvider(CodebaseContext context, Path rootDir) {
        this.context = context;
        this.rootDir = rootDir;
        this.constantResolver = context.getConstantResolver();
        this.eventDetector = new GenericEventDetector(context, constantResolver, context.getLibraryHints());
        this.mvcDetector = new SpringMvcDetector(context, constantResolver);
        this.messagingDetector = new MessagingDetector(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);
        
        this.callGraphEngine = new JdtCallGraphEngine(context, injectionAnalyzer);
        
        this.lifecycleDetector = new LifecycleDetector(context);
        this.interceptorDetector = new InterceptorDetector(context);
        this.componentDetector = new SpringComponentDetector(context);
    }

    @Override
    public List<TriggerPoint> findTriggerPoints() {
        if (cachedTriggers != null) {
            return cachedTriggers;
        }
        List<TriggerPoint> allTriggers = new ArrayList<>();
        var cus = context.getCompilationUnits();
        log.info("JdtIntelligenceProvider scanning {} compilation units for triggers", cus.size());
        for (CompilationUnit cu : cus) {
            allTriggers.addAll(eventDetector.detect(cu));
            allTriggers.addAll(lifecycleDetector.detect(cu));
        }
        log.info("Found {} triggers (including lifecycle) in total", allTriggers.size());
        cachedTriggers = allTriggers;
        return allTriggers;
    }

    @Override
    public List<EntryPoint> findEntryPoints() {
        if (cachedEntryPoints != null) {
            return cachedEntryPoints;
        }
        List<EntryPoint> allEntryPoints = new ArrayList<>();
        var cus = context.getCompilationUnits();
        log.info("JdtIntelligenceProvider scanning {} compilation units for entry points", cus.size());
        for (CompilationUnit cu : cus) {
            allEntryPoints.addAll(mvcDetector.detect(cu));
            allEntryPoints.addAll(messagingDetector.detect(cu));
            allEntryPoints.addAll(interceptorDetector.detect(cu));
            allEntryPoints.addAll(componentDetector.detect(cu));
        }
        log.info("Found {} entry points (including interceptors and listeners) in total", allEntryPoints.size());
        cachedEntryPoints = allEntryPoints;
        return allEntryPoints;
    }

    @Override
    public List<CallChain> findCallChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
        List<EntryPoint> scopedEntryPoints = entryPoints != null && !entryPoints.isEmpty()
                ? entryPoints
                : findEntryPoints();
        List<TriggerPoint> scopedTriggers = triggers != null && !triggers.isEmpty()
                ? triggers
                : findTriggerPoints();
        log.info("JdtIntelligenceProvider searching for call chains between {} entry points and {} triggers",
                scopedEntryPoints.size(), scopedTriggers.size());
        return callGraphEngine.findChains(scopedEntryPoints, scopedTriggers);
    }

    @Override
    public Map<String, Map<String, String>> resolveProperties() {
        return context.getProperties();
    }
}
