package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.ast.common.AstUtils;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.*;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Resolves expression values under path-binding context using the dataflow pipeline
 * ({@link VariableTracer} / {@link click.kamil.springstatemachineexporter.ast.common.JdtDataFlowModel})
 * instead of a parallel AST mini-interpreter.
 */
public class PathBindingExpressionResolver {

    private final CodebaseContext context;
    private final VariableTracer variableTracer;
    private final ConstantResolver constantResolver;
    private final TypeResolver typeResolver;

    public PathBindingExpressionResolver(
            CodebaseContext context,
            VariableTracer variableTracer,
            ConstantResolver constantResolver) {
        this(context, variableTracer, constantResolver, new TypeResolver(context));
    }

    PathBindingExpressionResolver(
            CodebaseContext context,
            VariableTracer variableTracer,
            ConstantResolver constantResolver,
            TypeResolver typeResolver) {
        this.context = context;
        this.variableTracer = variableTracer;
        this.constantResolver = constantResolver;
        this.typeResolver = typeResolver;
    }

    public String resolveMethodCallFromSource(String callerFqn, String varName, Map<String, String> bindings) {
        MethodDeclaration md = findMethodDeclaration(callerFqn);
        if (md == null || md.getBody() == null) {
            return null;
        }
        final Expression[] initializer = new Expression[1];
        md.getBody().accept(new ASTVisitor() {
            @Override
            public boolean visit(VariableDeclarationFragment node) {
                if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
                    initializer[0] = node.getInitializer();
                }
                return super.visit(node);
            }
        });
        if (initializer[0] == null) {
            return null;
        }
        return resolve(initializer[0], callerFqn, bindings);
    }

    public String resolve(Expression expr, String callerFqn, Map<String, String> bindings) {
        if (expr == null) {
            return null;
        }
        Map<String, String> effectiveBindings = bindings == null ? Map.of() : new HashMap<>(bindings);
        context.setAnalysisNamedBindings(effectiveBindings);
        try {
            return resolveInternal(expr, callerFqn, effectiveBindings);
        } finally {
            context.clearAnalysisNamedBindings();
        }
    }

    private String resolveInternal(Expression expr, String callerFqn, Map<String, String> bindings) {
        if (expr instanceof StringLiteral sl) {
            return "\"" + sl.getLiteralValue() + "\"";
        }
        if (expr instanceof QualifiedName qn) {
            return qn.getFullyQualifiedName();
        }
        if (expr instanceof SimpleName sn) {
            return bindings.getOrDefault(sn.getIdentifier(), sn.getIdentifier());
        }
        if (expr instanceof SwitchExpression se) {
            return constantResolver.evaluateSwitchWithParams(se, bindings, context);
        }
        if (expr instanceof MethodInvocation mi) {
            return resolveMethodInvocationReturn(mi, callerFqn, bindings);
        }
        return resolveViaDataflow(expr);
    }

    private String resolveMethodInvocationReturn(MethodInvocation mi, String callerFqn, Map<String, String> bindings) {
        String ownerFqn = resolveMethodOwnerFqn(mi, callerFqn);
        if (ownerFqn == null) {
            return null;
        }
        String methodName = mi.getName().getIdentifier();
        TypeDeclaration owner = context.getTypeDeclaration(ownerFqn);
        if (owner == null) {
            return null;
        }
        MethodDeclaration md = context.findMethodDeclaration(owner, methodName, true);
        if (md == null) {
            return null;
        }

        Map<String, String> callBindings = new HashMap<>(bindings);
        for (int i = 0; i < md.parameters().size() && i < mi.arguments().size(); i++) {
            SingleVariableDeclaration param = (SingleVariableDeclaration) md.parameters().get(i);
            String paramName = param.getName().getIdentifier();
            String argValue = resolveInternal((Expression) mi.arguments().get(i), callerFqn, callBindings);
            if (argValue != null) {
                callBindings.put(paramName, argValue);
            }
        }

        context.setAnalysisNamedBindings(callBindings);
        try {
            String viaDataflow = resolveViaDataflow(mi);
            if (viaDataflow != null && !viaDataflow.isBlank() && !AstUtils.isAnalysisToken(viaDataflow)) {
                return viaDataflow;
            }
            return constantResolver.evaluateMethodReturn(md, callBindings, context);
        } finally {
            context.setAnalysisNamedBindings(bindings);
        }
    }

    private String resolveViaDataflow(Expression expr) {
        Set<String> constants = new LinkedHashSet<>();
        for (Expression def : variableTracer.traceVariableAll(expr)) {
            String val = constantResolver.resolve(def, context);
            if (val != null && !val.isBlank()) {
                constants.add(val);
            }
        }
        if (constants.size() == 1) {
            return formatResolvedConstant(constants.iterator().next());
        }
        if (constants.isEmpty()) {
            String direct = constantResolver.resolve(expr, context);
            return direct != null ? formatResolvedConstant(direct) : null;
        }
        return null;
    }

    private static String formatResolvedConstant(String value) {
        if (value == null) {
            return null;
        }
        if (AstUtils.isAnalysisToken(value)) {
            return value;
        }
        if (value.startsWith("\"") || AstUtils.isEnumLikeConstantName(value)) {
            return value;
        }
        return "\"" + value + "\"";
    }

    private String resolveMethodOwnerFqn(MethodInvocation mi, String callerFqn) {
        IMethodBinding methodBinding = mi.resolveMethodBinding();
        if (methodBinding != null && methodBinding.getDeclaringClass() != null) {
            return methodBinding.getDeclaringClass().getErasure().getQualifiedName();
        }
        Expression receiver = mi.getExpression();
        if (receiver instanceof SimpleName sn) {
            return resolveFieldTypeFqn(callerFqn, sn.getIdentifier());
        }
        if (receiver instanceof FieldAccess fa) {
            IVariableBinding fieldBinding = fa.resolveFieldBinding();
            if (fieldBinding != null && fieldBinding.getType() != null) {
                return fieldBinding.getType().getErasure().getQualifiedName();
            }
            return resolveFieldTypeFqn(callerFqn, fa.getName().getIdentifier());
        }
        return null;
    }

    private String resolveFieldTypeFqn(String callerFqn, String fieldName) {
        if (callerFqn == null || !callerFqn.contains(".")) {
            return null;
        }
        String className = callerFqn.substring(0, callerFqn.lastIndexOf('.'));
        TypeDeclaration td = context.getTypeDeclaration(className);
        if (td == null) {
            return null;
        }
        CompilationUnit cu = td.getRoot() instanceof CompilationUnit c ? c : null;
        for (FieldDeclaration field : td.getFields()) {
            for (Object fragmentObj : field.fragments()) {
                VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragmentObj;
                if (fragment.getName().getIdentifier().equals(fieldName)) {
                    IVariableBinding binding = fragment.resolveBinding();
                    if (binding != null && binding.getType() != null && !binding.getType().isRecovered()) {
                        String fqn = binding.getType().getErasure().getQualifiedName();
                        if (fqn != null && fqn.contains(".") && !fqn.startsWith("<")) {
                            return fqn;
                        }
                    }
                    String viaTr = typeResolver.resolveTypeToFqn(field.getType(), cu);
                    if (viaTr != null && viaTr.contains(".")) {
                        return viaTr;
                    }
                    AbstractTypeDeclaration resolved =
                            context.getAbstractTypeDeclaration(field.getType().toString(), cu);
                    if (resolved != null) {
                        return context.getFqn(resolved);
                    }
                    return field.getType().toString();
                }
            }
        }
        return null;
    }

    private MethodDeclaration findMethodDeclaration(String methodFqn) {
        if (methodFqn == null || !methodFqn.contains(".")) {
            return null;
        }
        String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
        String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
        TypeDeclaration td = context.getTypeDeclaration(className);
        if (td == null) {
            return null;
        }
        return context.findMethodDeclaration(td, methodName, true);
    }
}
