package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.*;
import java.util.HashSet;
import java.util.Set;

/**
 * Single source-derived policy for marking REST triggers as external vs internal.
 */
public final class ExternalTriggerPolicy {

    private ExternalTriggerPolicy() {
    }

    public static boolean isExternalFromSource(
            EntryPoint entryPoint,
            TriggerPoint trigger,
            String entryMethodFqn,
            String resolvedEventParamName,
            CodebaseContext context) {
        if (trigger == null) {
            return false;
        }
        if (MachineEnumCanonicalizer.classifyTriggerEvent(trigger.getEvent())
                == MachineEnumCanonicalizer.TriggerEventKind.CANONICAL_ENUM) {
            return false;
        }
        if (trigger.getPolymorphicEvents() != null
                && !trigger.getPolymorphicEvents().isEmpty()
                && MachineEnumCanonicalizer.hasOnlyConcretePolymorphicEvents(trigger.getPolymorphicEvents())) {
            return false;
        }
        if (entryPoint != null && entryPoint.getType() == EntryPoint.Type.REST) {
            String paramName = resolveRestEventParameterName(entryPoint, resolvedEventParamName);
            if (paramName != null) {
                RestParamBinding binding = findRestParameterBinding(entryPoint, paramName, context);
                if (binding != null) {
                    if (binding.pathOrQueryVariable()) {
                        return true;
                    }
                    if (binding.requestBodyEnum()) {
                        return false;
                    }
                }
                if (entryPoint.getClassName() != null && entryPoint.getMethodName() != null) {
                    RestParamBinding requestBodyField = findRequestBodyEnumFieldBinding(
                            entryPoint.getClassName() + "." + entryPoint.getMethodName(), paramName, context);
                    if (requestBodyField != null && requestBodyField.requestBodyEnum()) {
                        return false;
                    }
                }
            }
            if (entryPoint.getName() != null && entryPoint.getName().contains("{")
                    && MachineEnumCanonicalizer.isDynamicTriggerExpression(trigger.getEvent())) {
                return true;
            }
        }
        if (entryMethodFqn != null && resolvedEventParamName != null) {
            RestParamBinding binding = findMethodParameterBinding(entryMethodFqn, resolvedEventParamName, context);
            if (binding != null) {
                if (binding.pathOrQueryVariable()) {
                    return true;
                }
                if (binding.requestBodyEnum()) {
                    return false;
                }
            }
            RestParamBinding requestBodyField = findRequestBodyEnumFieldBinding(
                    entryMethodFqn, resolvedEventParamName, context);
            if (requestBodyField != null && requestBodyField.requestBodyEnum()) {
                return false;
            }
        }
        return trigger.isExternal();
    }

    private static String resolveRestEventParameterName(EntryPoint entryPoint, String resolvedEventParamName) {
        if (resolvedEventParamName != null && !resolvedEventParamName.isBlank()) {
            return resolvedEventParamName;
        }
        if (entryPoint.getParameters() == null) {
            return null;
        }
        for (EntryPoint.Parameter parameter : entryPoint.getParameters()) {
            if (parameter.getAnnotations() == null) {
                continue;
            }
            for (String annotation : parameter.getAnnotations()) {
                if ("PathVariable".equals(annotation) || "RequestParam".equals(annotation)) {
                    return parameter.getName();
                }
            }
        }
        return null;
    }

    private static RestParamBinding findRestParameterBinding(
            EntryPoint entryPoint, String paramName, CodebaseContext context) {
        if (entryPoint.getParameters() == null || paramName == null) {
            return null;
        }
        for (EntryPoint.Parameter parameter : entryPoint.getParameters()) {
            if (!paramName.equals(parameter.getName())) {
                continue;
            }
            return RestParamBinding.fromEntryPointParameter(parameter);
        }
        if (entryPoint.getClassName() != null && entryPoint.getMethodName() != null) {
            return findMethodParameterBinding(
                    entryPoint.getClassName() + "." + entryPoint.getMethodName(), paramName, context);
        }
        return null;
    }

    private static RestParamBinding findMethodParameterBinding(
            String methodFqn, String paramName, CodebaseContext context) {
        if (methodFqn == null || !methodFqn.contains(".") || paramName == null || context == null) {
            return null;
        }
        String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
        String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(className);
        if (typeDeclaration == null) {
            return null;
        }
        MethodDeclaration method = context.findMethodDeclaration(typeDeclaration, methodName, true);
        if (method == null) {
            return null;
        }
        for (Object paramObj : method.parameters()) {
            if (!(paramObj instanceof SingleVariableDeclaration param)) {
                continue;
            }
            if (!paramName.equals(param.getName().getIdentifier())) {
                continue;
            }
            return RestParamBinding.fromAstParameter(param);
        }
        return null;
    }

    private static RestParamBinding findRequestBodyEnumFieldBinding(
            String methodFqn, String fieldName, CodebaseContext context) {
        if (methodFqn == null || !methodFqn.contains(".") || fieldName == null || context == null) {
            return null;
        }
        String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
        String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
        TypeDeclaration typeDeclaration = context.getTypeDeclaration(className);
        if (typeDeclaration == null) {
            return null;
        }
        MethodDeclaration method = context.findMethodDeclaration(typeDeclaration, methodName, true);
        if (method == null) {
            return null;
        }
        for (Object paramObj : method.parameters()) {
            if (!(paramObj instanceof SingleVariableDeclaration param)) {
                continue;
            }
            if (!hasParameterAstAnnotation(param, "RequestBody") || param.getType() == null) {
                continue;
            }
            ITypeBinding bodyBinding = param.getType().resolveBinding();
            if (bodyBinding == null || bodyBinding.isEnum()) {
                continue;
            }
            if (isEnumRecordComponent(bodyBinding, fieldName)) {
                return new RestParamBinding(false, false, true, true);
            }
            if (isEnumDtoField(bodyBinding, fieldName, context)) {
                return new RestParamBinding(false, false, true, true);
            }
            if (containsNestedEnumField(bodyBinding, fieldName, context)) {
                return new RestParamBinding(false, false, true, true);
            }
        }
        return null;
    }

    private static boolean isEnumRecordComponent(ITypeBinding bodyBinding, String fieldName) {
        if (bodyBinding == null || fieldName == null) {
            return false;
        }
        for (IVariableBinding component : bodyBinding.getDeclaredFields()) {
            if (!fieldName.equals(component.getName())) {
                continue;
            }
            ITypeBinding fieldType = component.getType();
            return fieldType != null && fieldType.isEnum();
        }
        return false;
    }

    private static boolean isEnumDtoField(ITypeBinding bodyBinding, String fieldName, CodebaseContext context) {
        if (bodyBinding == null || fieldName == null || context == null) {
            return false;
        }
        String bodyFqn = bodyBinding.getQualifiedName();
        if (bodyFqn == null) {
            return false;
        }
        AbstractTypeDeclaration bodyType = context.getAbstractTypeDeclaration(bodyFqn);
        if (!(bodyType instanceof TypeDeclaration td)) {
            return false;
        }
        for (FieldDeclaration field : td.getFields()) {
            for (Object fragObj : field.fragments()) {
                if (!(fragObj instanceof VariableDeclarationFragment fragment)) {
                    continue;
                }
                if (!fieldName.equals(fragment.getName().getIdentifier())) {
                    continue;
                }
                ITypeBinding fieldBinding = field.getType().resolveBinding();
                return fieldBinding != null && fieldBinding.isEnum();
            }
        }
        return false;
    }

    private static boolean containsNestedEnumField(
            ITypeBinding bodyBinding, String fieldName, CodebaseContext context) {
        return containsNestedEnumField(bodyBinding, fieldName, context, new HashSet<>());
    }

    private static boolean containsNestedEnumField(
            ITypeBinding bodyBinding,
            String fieldName,
            CodebaseContext context,
            Set<String> visited) {
        if (bodyBinding == null || fieldName == null || context == null) {
            return false;
        }
        String bodyFqn = bodyBinding.getQualifiedName();
        if (bodyFqn == null || !visited.add(bodyFqn)) {
            return false;
        }
        for (IVariableBinding component : bodyBinding.getDeclaredFields()) {
            ITypeBinding nestedType = component.getType();
            if (nestedType == null || nestedType.isPrimitive() || nestedType.isEnum()) {
                continue;
            }
            if (isEnumRecordComponent(nestedType, fieldName) || isEnumDtoField(nestedType, fieldName, context)) {
                return true;
            }
            if (containsNestedEnumField(nestedType, fieldName, context, visited)) {
                return true;
            }
        }
        return false;
    }

    private static boolean hasParameterAstAnnotation(SingleVariableDeclaration param, String simpleName) {
        for (Object modifier : param.modifiers()) {
            if (modifier instanceof Annotation annotation
                    && annotation.getTypeName().getFullyQualifiedName().endsWith(simpleName)) {
                return true;
            }
        }
        return false;
    }

    private record RestParamBinding(boolean pathVariable, boolean requestParam, boolean requestBody, boolean enumType) {
        boolean pathOrQueryVariable() {
            return pathVariable || requestParam;
        }

        boolean requestBodyEnum() {
            return requestBody && enumType;
        }

        static RestParamBinding fromEntryPointParameter(EntryPoint.Parameter parameter) {
            boolean pathVariable = hasAnnotation(parameter.getAnnotations(), "PathVariable");
            boolean requestParam = hasAnnotation(parameter.getAnnotations(), "RequestParam");
            boolean requestBody = hasAnnotation(parameter.getAnnotations(), "RequestBody");
            boolean enumType = isEnumTypeName(parameter.getType());
            return new RestParamBinding(pathVariable, requestParam, requestBody, enumType);
        }

        static RestParamBinding fromAstParameter(SingleVariableDeclaration param) {
            boolean pathVariable = hasParameterAstAnnotation(param, "PathVariable");
            boolean requestParam = hasParameterAstAnnotation(param, "RequestParam");
            boolean requestBody = hasParameterAstAnnotation(param, "RequestBody");
            boolean enumType = param.getType() != null && param.getType().resolveBinding() != null
                    && param.getType().resolveBinding().isEnum();
            return new RestParamBinding(pathVariable, requestParam, requestBody, enumType);
        }

        private static boolean hasAnnotation(java.util.List<String> annotations, String name) {
            return annotations != null && annotations.stream().anyMatch(name::equals);
        }

        private static boolean isEnumTypeName(String typeName) {
            if (typeName == null || typeName.isBlank()) {
                return false;
            }
            int lastDot = typeName.lastIndexOf('.');
            String simple = lastDot >= 0 ? typeName.substring(lastDot + 1) : typeName;
            return Character.isUpperCase(simple.charAt(0)) && !simple.endsWith("String");
        }
    }
}
