package click.kamil.springstatemachineexporter.analysis.enricher.routing;

import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
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.*;

import java.util.Set;

/**
 * Resolves which {@code @EnableStateMachine} configuration owns a trigger's {@code StateMachine} receiver
 * using Spring injection analysis (JDT bindings), not package-name guessing.
 */
final class SpringInjectionRouting {

    private static final Set<String> TRIGGER_METHODS = Set.of(
            "sendEvent", "sendEvents", "sendEventCollect", "sendEventMono", "fire", "trigger");

    private SpringInjectionRouting() {
    }

    static Boolean matchesMachineConfig(TriggerPoint trigger, String machineConfigFqn, CodebaseContext context) {
        if (trigger == null || machineConfigFqn == null || context == null || !context.isResolveBindings()) {
            return null;
        }
        String configFqn = resolveConfigurationFqn(trigger, context);
        if (configFqn == null) {
            return null;
        }
        return machineConfigFqn.equals(configFqn);
    }

    static String resolveConfigurationFqn(TriggerPoint trigger, CodebaseContext context) {
        if (trigger == null || trigger.getClassName() == null || trigger.getMethodName() == null) {
            return null;
        }
        if (!context.isResolveBindings()) {
            return null;
        }
        InjectionPointAnalyzer analyzer = createAnalyzer(context);
        TypeDeclaration type = context.getTypeDeclaration(trigger.getClassName());
        if (type == null) {
            return null;
        }
        MethodDeclaration method = context.findMethodDeclaration(type, trigger.getMethodName(), true);
        if (method == null || method.getBody() == null) {
            return null;
        }
        CompilationUnit cu = (CompilationUnit) type.getRoot();
        final IVariableBinding[] receiverBinding = new IVariableBinding[1];
        method.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                if (trigger.getLineNumber() > 0 && cu != null
                        && cu.getLineNumber(node.getStartPosition()) != trigger.getLineNumber()) {
                    return true;
                }
                if (!TRIGGER_METHODS.contains(node.getName().getIdentifier())) {
                    return true;
                }
                Expression receiver = node.getExpression();
                if (receiver instanceof SimpleName sn) {
                    IBinding binding = sn.resolveBinding();
                    if (binding instanceof IVariableBinding vb) {
                        receiverBinding[0] = vb;
                    }
                } else if (receiver instanceof FieldAccess fa) {
                    receiverBinding[0] = fa.resolveFieldBinding();
                }
                return false;
            }
        });
        if (receiverBinding[0] == null) {
            return null;
        }
        String injectedFqn = analyzer.resolveInjectedBeanFqn(receiverBinding[0]);
        if (injectedFqn == null) {
            return null;
        }
        TypeDeclaration injectedType = context.getTypeDeclaration(injectedFqn);
        if (injectedType != null && context.extendsStateMachineConfigurerAdapter(injectedType)) {
            return injectedFqn;
        }
        return EnableStateMachineBeanRouting.findConfigFqnByBeanName(
                EnableStateMachineBeanRouting.defaultBeanNameFromFqn(injectedFqn), context);
    }

    private static InjectionPointAnalyzer createAnalyzer(CodebaseContext context) {
        SpringBeanRegistry registry = new SpringBeanRegistry();
        SpringContextScanner scanner = new SpringContextScanner(registry);
        for (CompilationUnit cu : context.getCompilationUnits()) {
            cu.accept(scanner);
        }
        return new InjectionPointAnalyzer(new SpringDependencyResolver(registry));
    }
}
