package click.kamil.springstatemachineexporter.analysis.resolver;

import click.kamil.springstatemachineexporter.analysis.service.TypeResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.*;

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

/**
 * Resolves {@code StateMachineConfigurerAdapter<S, E>} type arguments for a config class.
 * Used by analysis (canonical enum ids) and routing (machine scoping).
 */
public final class StateMachineTypeResolver {

    public record MachineTypes(String stateTypeFqn, String eventTypeFqn) {
        public static MachineTypes empty() {
            return new MachineTypes(null, null);
        }
    }

    private StateMachineTypeResolver() {
    }

    public static String[] resolve(String machineConfigFqn, CodebaseContext context) {
        MachineTypes types = resolveTypes(machineConfigFqn, context);
        return new String[]{types.stateTypeFqn(), types.eventTypeFqn()};
    }

    public static MachineTypes resolveTypes(String machineConfigFqn, CodebaseContext context) {
        if (machineConfigFqn == null || context == null) {
            return MachineTypes.empty();
        }
        String configFqn = machineConfigFqn;
        if (configFqn.contains("#")) {
            configFqn = configFqn.substring(0, configFqn.indexOf('#'));
        }
        TypeDeclaration td = context.getTypeDeclaration(configFqn);
        if (td == null) {
            return MachineTypes.empty();
        }
        Set<String> visited = new HashSet<>();
        visited.add(configFqn);
        String[] args = findTypeArgumentsRecursively(
                td.getSuperclassType(),
                context,
                (CompilationUnit) td.getRoot(),
                visited,
                Map.of());
        return new MachineTypes(args[0], args[1]);
    }

    public static boolean extendsEnumStateMachineConfigurer(String machineConfigFqn, CodebaseContext context) {
        if (machineConfigFqn == null || context == null) {
            return false;
        }
        String configFqn = machineConfigFqn;
        if (configFqn.contains("#")) {
            configFqn = configFqn.substring(0, configFqn.indexOf('#'));
        }
        TypeDeclaration td = context.getTypeDeclaration(configFqn);
        if (td == null) {
            return false;
        }
        Set<String> visited = new HashSet<>();
        visited.add(configFqn);
        return hierarchyHasEnumConfigurer(
                td.getSuperclassType(),
                context,
                (CompilationUnit) td.getRoot(),
                visited);
    }

    private static boolean hierarchyHasEnumConfigurer(
            Type type,
            CodebaseContext context,
            CompilationUnit cu,
            Set<String> visited) {
        if (type == null) {
            return false;
        }
        if (type instanceof ParameterizedType pt) {
            String rawTypeName = pt.getType().toString();
            if (rawTypeName.equals("EnumStateMachineConfigurerAdapter")
                    || rawTypeName.endsWith(".EnumStateMachineConfigurerAdapter")) {
                return true;
            }
            AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(rawTypeName, cu);
            if (td != null) {
                String fqn = context.getFqn(td);
                if (visited.add(fqn)) {
                    Type superclass = td instanceof TypeDeclaration typeDecl ? typeDecl.getSuperclassType() : null;
                    if (hierarchyHasEnumConfigurer(superclass, context, cu, visited)) {
                        return true;
                    }
                }
            }
        } else {
            String simpleName = type.toString();
            AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(simpleName, cu);
            if (td != null) {
                String fqn = context.getFqn(td);
                if (visited.add(fqn)) {
                    Type superclass = td instanceof TypeDeclaration typeDecl ? typeDecl.getSuperclassType() : null;
                    if (hierarchyHasEnumConfigurer(superclass, context, cu, visited)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private static String[] findTypeArgumentsRecursively(
            Type type,
            CodebaseContext context,
            CompilationUnit cu,
            Set<String> visited,
            Map<String, String> typeVarBindings) {
        if (type == null) {
            return new String[]{null, null};
        }

        if (type instanceof ParameterizedType pt) {
            String rawTypeName = pt.getType().toString();
            List<?> typeArgs = pt.typeArguments();

            if (isStateMachineConfigurerAdapter(rawTypeName)) {
                if (typeArgs.size() >= 2) {
                    String stateType = resolveTypeToFqn((Type) typeArgs.get(0), cu, context, typeVarBindings);
                    String eventType = resolveTypeToFqn((Type) typeArgs.get(1), cu, context, typeVarBindings);
                    return new String[]{stateType, eventType};
                }
            }

            Map<String, String> nextBindings = extendBindings(rawTypeName, typeArgs, cu, context, typeVarBindings);

            AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(rawTypeName, cu);
            if (td != null) {
                String fqn = context.getFqn(td);
                if (visited.add(fqn)) {
                    Type superclass = td instanceof TypeDeclaration typeDecl ? typeDecl.getSuperclassType() : null;
                    String[] res = findTypeArgumentsRecursively(superclass, context, cu, visited, nextBindings);
                    if (res[0] != null || res[1] != null) {
                        return res;
                    }
                }
            }
        } else {
            String simpleName = type.toString();
            AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(simpleName, cu);
            if (td != null) {
                String fqn = context.getFqn(td);
                if (visited.add(fqn)) {
                    Type superclass = td instanceof TypeDeclaration typeDecl ? typeDecl.getSuperclassType() : null;
                    String[] res = findTypeArgumentsRecursively(superclass, context, cu, visited, typeVarBindings);
                    if (res[0] != null || res[1] != null) {
                        return res;
                    }
                }
            }
        }

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

    private static Map<String, String> extendBindings(
            String rawTypeName,
            List<?> typeArgs,
            CompilationUnit cu,
            CodebaseContext context,
            Map<String, String> parentBindings) {
        Map<String, String> bindings = new HashMap<>(parentBindings);
        AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(rawTypeName, cu);
        if (!(td instanceof TypeDeclaration typeDecl) || typeArgs.isEmpty()) {
            return bindings;
        }
        List<TypeParameter> typeParameters = typeDecl.typeParameters();
        for (int i = 0; i < Math.min(typeParameters.size(), typeArgs.size()); i++) {
            String paramName = typeParameters.get(i).getName().getIdentifier();
            String resolvedArg = resolveTypeToFqn((Type) typeArgs.get(i), cu, context, parentBindings);
            if (resolvedArg != null) {
                bindings.put(paramName, resolvedArg);
            }
        }
        return bindings;
    }

    private static boolean isStateMachineConfigurerAdapter(String rawTypeName) {
        return rawTypeName.equals("StateMachineConfigurerAdapter")
                || rawTypeName.equals("EnumStateMachineConfigurerAdapter")
                || rawTypeName.endsWith(".StateMachineConfigurerAdapter")
                || rawTypeName.endsWith(".EnumStateMachineConfigurerAdapter");
    }

    static String resolveTypeToFqn(Type type, CompilationUnit cu, CodebaseContext context) {
        return resolveTypeToFqn(type, cu, context, Map.of());
    }

    static String resolveTypeToFqn(
            Type type,
            CompilationUnit cu,
            CodebaseContext context,
            Map<String, String> typeVarBindings) {
        return new TypeResolver(context).resolveTypeToFqn(type, cu, typeVarBindings);
    }
}
