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

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;

import java.util.HashSet;
import java.util.Set;

public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {

    @Override
    public boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName, CodebaseContext context) {
        // Precise FQN Type argument match
        if (chain.getTriggerPoint() != null && context != null && currentMachineName != null) {
            String triggerEventFqn = chain.getTriggerPoint().getEventTypeFqn();
            String triggerStateFqn = chain.getTriggerPoint().getStateTypeFqn();
            if (triggerEventFqn != null || triggerStateFqn != null) {
                String[] machineTypes = getStateMachineTypeArguments(currentMachineName, context);
                String machineStateFqn = machineTypes[0];
                String machineEventFqn = machineTypes[1];
                
                if (triggerEventFqn != null && machineEventFqn != null) {
                    if (eraseGenerics(triggerEventFqn).equals(eraseGenerics(machineEventFqn))) {
                        return true; // Match!
                    }
                    if (isTypeMismatched(triggerEventFqn, machineEventFqn)) {
                        return false; // Mismatch!
                    }
                }
                if (triggerStateFqn != null && machineStateFqn != null) {
                    if (eraseGenerics(triggerStateFqn).equals(eraseGenerics(machineStateFqn))) {
                        return true; // Match!
                    }
                    if (isTypeMismatched(triggerStateFqn, machineStateFqn)) {
                        return false; // Mismatch!
                    }
                }
            }
        }

        String targetVar = chain.getContextMachineId();
        if (targetVar == null && chain.getTriggerPoint() != null) {
            targetVar = chain.getTriggerPoint().getStateMachineId();
        }
        
        String simplifiedMachineName = currentMachineName != null ? currentMachineName.substring(currentMachineName.lastIndexOf('.') + 1).toLowerCase() : "";
        
        if (targetVar != null && !targetVar.isEmpty()) {
            targetVar = targetVar.toLowerCase();
            if (targetVar.endsWith("statemachine")) {
                String prefix = targetVar.substring(0, targetVar.length() - "statemachine".length());
                if (!prefix.isEmpty()) {
                    if (simplifiedMachineName.contains(prefix)) {
                        return true; // Explicit positive match
                    } else {
                        return false; // Explicit negative match
                    }
                }
            } else if (simplifiedMachineName.contains(targetVar)) {
                 return true;
            }
        }

        if (chain.getMethodChain() != null && !chain.getMethodChain().isEmpty() && currentMachineName != null) {
            String smPackage = getPackageName(currentMachineName);
            String smSimple = getSimpleClassName(currentMachineName);
            String smPrefix = getFirstCamelCaseWord(smSimple);
            
            boolean hasPositiveMatch = false;
            boolean hasStrongMismatch = false;

            if (chain.getTriggerPoint() != null && chain.getTriggerPoint().getClassName() != null) {
                String chainClass = chain.getTriggerPoint().getClassName();
                String chainPackage = getPackageName(chainClass);
                String chainSimple = getSimpleClassName(chainClass);
                String chainPrefix = getFirstCamelCaseWord(chainSimple);
                
                if (smPrefix != null && chainPrefix != null && smPrefix.equals(chainPrefix)) {
                    hasPositiveMatch = true;
                }
                
                if (isDomainMatch(smPackage, chainPackage, smPrefix, chainPrefix)) {
                    hasPositiveMatch = true;
                }
            }

            for (String method : chain.getMethodChain()) {
                String chainClass = getClassNameOnly(method);
                String chainPackage = getPackageName(chainClass);
                String chainSimple = getSimpleClassName(chainClass);
                String chainPrefix = getFirstCamelCaseWord(chainSimple);
                
                if (smPrefix != null && chainPrefix != null && smPrefix.equals(chainPrefix)) {
                    hasPositiveMatch = true;
                }
                
                if (isDomainMatch(smPackage, chainPackage, smPrefix, chainPrefix)) {
                    hasPositiveMatch = true;
                }
                
                if (isDomainMismatch(smPackage, chainPackage, smPrefix, chainPrefix, smSimple, chainSimple)) {
                    hasStrongMismatch = true;
                }
            }
            
            if (hasPositiveMatch) {
                return true;
            }
            if (hasStrongMismatch) {
                return false;
            }
        }
        return true;
    }

    private boolean isDomainMatch(String smPackage, String chainPackage, String smPrefix, String chainPrefix) {
        if (smPackage == null || chainPackage == null || smPackage.isEmpty() || chainPackage.isEmpty()) return false;
        
        if (smPackage.startsWith(chainPackage) || chainPackage.startsWith(smPackage)) {
            return true;
        }
        
        // Find deepest common package root
        String[] p1 = smPackage.split("\\.");
        String[] p2 = chainPackage.split("\\.");
        int matchIdx = -1;
        for (int i = 0; i < Math.min(p1.length, p2.length); i++) {
            if (p1[i].equals(p2[i])) {
                matchIdx = i;
            } else {
                break;
            }
        }
        
        // If the state machine's prefix is part of the shared common package, 
        // then the state machine represents the parent domain of the caller.
        if (smPrefix != null && matchIdx >= 0) {
            String lowerPrefix = smPrefix.toLowerCase();
            for (int i = 0; i <= matchIdx; i++) {
                if (p1[i].toLowerCase().contains(lowerPrefix) || lowerPrefix.contains(p1[i].toLowerCase())) {
                    return true;
                }
            }
        }
        
        return false;
    }
    
    private boolean isDomainMismatch(String smPackage, String chainPackage, String smPrefix, String chainPrefix, String smSimple, String chainSimple) {
        if (smPackage == null || chainPackage == null || smPackage.isEmpty() || chainPackage.isEmpty()) return false;
        if (smPrefix == null || chainPrefix == null) return false;

        String smLower = smPrefix.toLowerCase();
        String chainLower = chainPrefix.toLowerCase();

        // Ignore generic/technical class prefixes
        if (smLower.equals("state") || smLower.equals("statemachine") || smLower.equals("config") || smLower.equals("configuration") || smLower.equals("adapter") ||
            smLower.equals("enterprise") || smLower.equals("app") || smLower.equals("global") || smLower.equals("core") || smLower.equals("project") || smLower.equals("main") || smLower.equals("base") || smLower.equals("common") || smLower.equals("shared") ||
            chainLower.equals("state") || chainLower.equals("statemachine") || chainLower.equals("config") || chainLower.equals("configuration") || chainLower.equals("adapter") ||
            chainLower.equals("enterprise") || chainLower.equals("app") || chainLower.equals("global") || chainLower.equals("core") || chainLower.equals("project") || chainLower.equals("main") || chainLower.equals("base") || chainLower.equals("common") || chainLower.equals("shared")) {
            return false;
        }

        // If class prefixes diverge, check if they are from different domains
        if (!smLower.equals(chainLower)) {
            // If one prefix is present as a domain/feature segment in the other package, they are related
            if (chainPackage.toLowerCase().contains(smLower) || smPackage.toLowerCase().contains(chainLower)) {
                return false;
            }

            // Otherwise, if they diverge under a shared project package root, it is a mismatch
            String[] p1 = smPackage.split("\\.");
            String[] p2 = chainPackage.split("\\.");
            int matchIdx = -1;
            for (int i = 0; i < Math.min(p1.length, p2.length); i++) {
                if (p1[i].equals(p2[i])) {
                    matchIdx = i;
                } else {
                    break;
                }
            }
            if (matchIdx >= 1) {
                // If they diverge under a shared root, check if the divergence is only on generic package layers
                boolean onlyGenericDivergence = true;
                Set<String> genericLayers = Set.of(
                    "config", "configuration", "service", "services", "impl", "api", "web", 
                    "controller", "controllers", "messaging", "listener", "listeners", 
                    "repository", "repositories", "db", "model", "models", "domain", 
                    "constants", "utils", "helper", "helpers", "event", "events", 
                    "state", "states", "transition", "transitions"
                );
                for (int i = matchIdx + 1; i < p1.length; i++) {
                    if (!genericLayers.contains(p1[i].toLowerCase())) {
                        onlyGenericDivergence = false;
                        break;
                    }
                }
                if (onlyGenericDivergence) {
                    for (int i = matchIdx + 1; i < p2.length; i++) {
                        if (!genericLayers.contains(p2[i].toLowerCase())) {
                            onlyGenericDivergence = false;
                            break;
                        }
                    }
                }
                
                if (!onlyGenericDivergence) {
                    return true; // Mismatch under a shared root
                }
            }
        }

        return false;
    }

    private Set<String> extractDomainTerms(String pkg, String prefix) {
        Set<String> terms = new HashSet<>();
        if (pkg != null && !pkg.isEmpty()) {
            String[] segments = pkg.split("\\.");
            // Take segments from index 2 onwards if length > 2
            int start = segments.length > 2 ? 2 : 0;
            for (int i = start; i < segments.length; i++) {
                terms.add(segments[i].toLowerCase());
            }
        }
        if (prefix != null && !prefix.isEmpty()) {
            terms.add(prefix.toLowerCase());
        }
        return terms;
    }

    private String getPackageName(String fqn) {
        if (fqn == null || !fqn.contains(".")) return "";
        return fqn.substring(0, fqn.lastIndexOf('.'));
    }

    private String getSimpleClassName(String fqn) {
        if (fqn == null) return "";
        if (!fqn.contains(".")) return fqn;
        return fqn.substring(fqn.lastIndexOf('.') + 1);
    }

    private String getClassNameOnly(String methodFqn) {
        if (methodFqn == null) return "";
        String clean = methodFqn;
        if (clean.contains("(")) {
            clean = clean.substring(0, clean.indexOf('('));
        }
        if (clean.contains(".")) {
            clean = clean.substring(0, clean.lastIndexOf('.'));
        }
        return clean;
    }

    private String getFirstCamelCaseWord(String simpleName) {
        if (simpleName == null || simpleName.isEmpty()) return null;
        String[] words = simpleName.split("(?<!^)(?=[A-Z])");
        if (words.length > 0) {
            return words[0];
        }
        return null;
    }

    private String[] getStateMachineTypeArguments(String machineName, CodebaseContext context) {
        org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration(machineName);
        if (td != null) {
            org.eclipse.jdt.core.dom.Type superclass = td.getSuperclassType();
            Set<String> visited = new HashSet<>();
            visited.add(machineName);
            return findTypeArgumentsRecursively(superclass, context, (org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot(), visited);
        }
        return new String[]{null, null};
    }

    private String[] findTypeArgumentsRecursively(org.eclipse.jdt.core.dom.Type type, CodebaseContext context, org.eclipse.jdt.core.dom.CompilationUnit cu, Set<String> visited) {
        if (type == null) return new String[]{null, null};

        if (type instanceof org.eclipse.jdt.core.dom.ParameterizedType pt) {
            String rawTypeName = pt.getType().toString();
            if (rawTypeName.equals("StateMachineConfigurerAdapter") || rawTypeName.equals("EnumStateMachineConfigurerAdapter") ||
                rawTypeName.endsWith(".StateMachineConfigurerAdapter") || rawTypeName.endsWith(".EnumStateMachineConfigurerAdapter")) {
                java.util.List<?> typeArgs = pt.typeArguments();
                if (typeArgs.size() >= 2) {
                    String stateType = resolveTypeToFqn((org.eclipse.jdt.core.dom.Type) typeArgs.get(0), cu, context);
                    String eventType = resolveTypeToFqn((org.eclipse.jdt.core.dom.Type) typeArgs.get(1), cu, context);
                    return new String[]{stateType, eventType};
                }
            }
            
            // Recurse into the raw type declaration's superclass
            org.eclipse.jdt.core.dom.AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(rawTypeName, cu);
            if (td != null) {
                String fqn = context.getFqn(td);
                if (visited.add(fqn)) {
                    org.eclipse.jdt.core.dom.Type superclass = (td instanceof org.eclipse.jdt.core.dom.TypeDeclaration) ? ((org.eclipse.jdt.core.dom.TypeDeclaration) td).getSuperclassType() : null;
                    String[] res = findTypeArgumentsRecursively(superclass, context, (org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot(), visited);
                    if (res[0] != null || res[1] != null) return res;
                }
            }
        } else {
            // It's a simple type (e.g. MyBaseConfig)
            String simpleName = type.toString();
            org.eclipse.jdt.core.dom.AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(simpleName, cu);
            if (td != null) {
                String fqn = context.getFqn(td);
                if (visited.add(fqn)) {
                    org.eclipse.jdt.core.dom.Type superclass = (td instanceof org.eclipse.jdt.core.dom.TypeDeclaration) ? ((org.eclipse.jdt.core.dom.TypeDeclaration) td).getSuperclassType() : null;
                    String[] res = findTypeArgumentsRecursively(superclass, context, (org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot(), visited);
                    if (res[0] != null || res[1] != null) return res;
                }
            }
        }

        return new String[]{null, null};
    }

    private String eraseGenerics(String type) {
        if (type == null) return null;
        int idx = type.indexOf('<');
        if (idx != -1) {
            return type.substring(0, idx);
        }
        return type;
    }

    private String resolveTypeToFqn(org.eclipse.jdt.core.dom.Type type, org.eclipse.jdt.core.dom.CompilationUnit cu, CodebaseContext context) {
        if (type == null) return null;
        String simpleName;
        if (type.isSimpleType()) {
            simpleName = ((org.eclipse.jdt.core.dom.SimpleType) type).getName().getFullyQualifiedName();
        } else if (type.isParameterizedType()) {
            return resolveTypeToFqn(((org.eclipse.jdt.core.dom.ParameterizedType) type).getType(), cu, context);
        } else {
            simpleName = type.toString();
        }

        if (simpleName.contains("<")) {
            simpleName = simpleName.substring(0, simpleName.indexOf('<'));
        }

        org.eclipse.jdt.core.dom.AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(simpleName, cu);
        if (td != null) {
            return context.getFqn(td);
        }
        
        for (Object impObj : cu.imports()) {
            org.eclipse.jdt.core.dom.ImportDeclaration imp = (org.eclipse.jdt.core.dom.ImportDeclaration) impObj;
            String impName = imp.getName().getFullyQualifiedName();
            if (impName.endsWith("." + simpleName)) {
                return impName;
            }
        }

        String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
        if (!packageName.isEmpty()) {
            org.eclipse.jdt.core.dom.AbstractTypeDeclaration localTd = context.getAbstractTypeDeclaration(packageName + "." + simpleName);
            if (localTd != null) return context.getFqn(localTd);
        }

        return simpleName;
    }

    private boolean isTypeMismatched(String type1, String type2) {
        if (type1 == null || type2 == null) return false;
        type1 = eraseGenerics(type1);
        type2 = eraseGenerics(type2);
        if (type1.length() == 1 || type2.length() == 1) return false;
        if (type1.equals("java.lang.String") || type1.equals("String") || type1.equals("java.lang.Object") || type1.equals("Object")) return false;
        if (type2.equals("java.lang.String") || type2.equals("String") || type2.equals("java.lang.Object") || type2.equals("Object")) return false;
        return !type1.equals(type2);
    }

    private boolean isSingleStateMachine(CodebaseContext context) {
        if (context == null) return false;
        int count = 0;
        count += context.findEntryPointClasses(java.util.List.of("EnableStateMachineFactory", "EnableStateMachine")).size();
        count += context.findBeanMethodsReturning(java.util.Set.of("StateMachine", "StateMachineFactory", "StateMachineModelFactory")).size();
        return count <= 1;
    }
}
