package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.pipeline.MessageCarrierSupport;
import click.kamil.springstatemachineexporter.analysis.pipeline.ReactiveExpressionSupport;
import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jdt.core.dom.*;

@Slf4j
public class JdtCallGraphEngine extends AbstractCallGraphEngine {
    private final InjectionPointAnalyzer injectionAnalyzer;

    public JdtCallGraphEngine(CodebaseContext context, InjectionPointAnalyzer injectionAnalyzer) {
        super(context);
        this.injectionAnalyzer = injectionAnalyzer;
        if (allocationFactPropagator != null) {
            allocationFactPropagator.setInjectionAnalyzer(injectionAnalyzer);
        }
    }

    @Override
    protected String callGraphCacheKey() {
        return "jdtCallGraph";
    }

    @Override
    protected String resolveArgument(Expression expr) {
        if (expr == null) {
            return "null";
        }
        Expression unwrappedBuilder = unwrapMessageBuilder(expr);
        if (unwrappedBuilder != expr) {
            expr = unwrappedBuilder;
        }

        Expression tracedExpr = traceVariable(expr);
        if (tracedExpr instanceof QualifiedName
                || tracedExpr instanceof ClassInstanceCreation
                || tracedExpr instanceof StringLiteral
                || tracedExpr instanceof NumberLiteral) {
            expr = tracedExpr;
        }

        String result = super.resolveArgument(expr);
        if (result != null) {
            result = result.replaceAll("->\\s*yield\\s+", "-> ");
        }
        return result;
    }

    @Override
    protected String resolveCalledMethod(MethodInvocation node) {
        Expression receiver = node.getExpression();
        String methodName = node.getName().getIdentifier();

        if (receiver instanceof MethodInvocation miReceiver) {
            String returnType = resolveMethodInvocationReturnType(miReceiver);
            if (returnType != null) {
                return returnType + "." + methodName;
            }
        }

        TypeDeclaration enclosingType = findEnclosingType(node);

        if (receiver instanceof SuperMethodInvocation) {
            return InheritanceCallTargetResolver.resolveSuperMethod(context, enclosingType, methodName);
        }

        if (receiver == null) {
            return InheritanceCallTargetResolver.resolveInstanceMethod(context, enclosingType, methodName);
        }

        if (injectionAnalyzer != null) {
            IBinding nameBinding = null;
            if (receiver instanceof SimpleName sn) {
                nameBinding = sn.resolveBinding();
            } else if (receiver instanceof FieldAccess fa) {
                nameBinding = fa.resolveFieldBinding();
            }
            if (nameBinding instanceof IVariableBinding varBinding) {
                if (log.isDebugEnabled()) {
                    log.debug("CALLGRAPH RESOLVING BEAN FOR BINDING: {} calling {}", varBinding.getName(), methodName);
                }
                String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
                if (concreteFqn == null) {
                    concreteFqn = ConstructorInjectionTypeNarrower.resolveConcreteReceiverTypeFqn(
                            varBinding,
                            enclosingType,
                            context,
                            injectionAnalyzer,
                            constructorAnalyzer);
                }
                if (concreteFqn != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("  -> RESOLVED CONCRETE FQN: {}", concreteFqn);
                    }
                    return concreteFqn + "." + methodName;
                } else if (log.isDebugEnabled()) {
                    log.debug("  -> RESOLVE FAILED, RETURNED NULL");
                }
            } else if (log.isDebugEnabled()) {
                log.debug("CALLGRAPH NAME BINDING IS NOT VARIABLE: {} calling {}", nameBinding, methodName);
            }
        }

        ITypeBinding binding = receiver.resolveTypeBinding();
        if (binding != null) {
            String typeName = null;
            try {
                if (binding.getErasure() != null) {
                    typeName = binding.getErasure().getQualifiedName();
                    if (typeName == null || typeName.isEmpty()) {
                        typeName = binding.getErasure().getName();
                    }
                }
            } catch (Exception ignored) {
                // Ignore JDT internal exceptions
            }

            if (typeName == null || typeName.isEmpty()) {
                typeName = binding.getQualifiedName();
                if (typeName == null || typeName.isEmpty()) {
                    typeName = binding.getName();
                }
                if (typeName != null && typeName.contains("<")) {
                    typeName = typeName.replaceAll("<.*>", "");
                }
            }

            if (typeName != null && !typeName.isEmpty()) {
                TypeDeclaration resolvedTd = context.getTypeDeclaration(typeName);
                if (resolvedTd != null && context.findMethodDeclaration(resolvedTd, methodName, true) != null) {
                    return typeName + "." + methodName;
                }
            }
        }

        if (receiver instanceof ThisExpression) {
            return InheritanceCallTargetResolver.resolveInstanceMethod(context, enclosingType, methodName);
        }

        if (receiver instanceof SimpleName sn) {
            String fallbackTypeFqn = resolveReceiverTypeFallback(sn);
            if (fallbackTypeFqn != null) {
                return fallbackTypeFqn + "." + methodName;
            }
            return sn.getIdentifier() + "." + methodName;
        }

        return null;
    }

    @Override
    protected String resolveInjectedReceiverTypeFqn(Expression receiver) {
        if (injectionAnalyzer == null || receiver == null) {
            return null;
        }
        IBinding nameBinding = null;
        if (receiver instanceof SimpleName sn) {
            nameBinding = sn.resolveBinding();
        } else if (receiver instanceof FieldAccess fa) {
            nameBinding = fa.resolveFieldBinding();
        }
        if (!(nameBinding instanceof IVariableBinding varBinding)) {
            return null;
        }
        String direct = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
        if (direct != null) {
            return direct;
        }
        TypeDeclaration enclosingType = findEnclosingType(receiver);
        return ConstructorInjectionTypeNarrower.resolveConcreteReceiverTypeFqn(
                varBinding,
                enclosingType,
                context,
                injectionAnalyzer,
                constructorAnalyzer);
    }

    private Expression unwrapMessageBuilder(Expression expr) {
        Expression peeled = MessageCarrierSupport.peelEventFromMessageExpr(expr);
        if (peeled != null) {
            return peeled;
        }
        Expression factory = ReactiveExpressionSupport.peelFactoryPayloadExpression(expr);
        return factory != null ? factory : expr;
    }
}
