package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jdt.core.dom.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@RequiredArgsConstructor
public class SpringMvcDetector {
    private final CodebaseContext context;
    private final ConstantResolver constantResolver;

    public List<EntryPoint> detect(CompilationUnit cu) {
        List<EntryPoint> entryPoints = new ArrayList<>();
        cu.accept(new ASTVisitor() {
            @Override
            public boolean visit(TypeDeclaration node) {
                if (isRestController(node) && !node.isInterface()) {
                    processController(node, entryPoints);
                }
                // Support WebFlux RouterFunction beans
                processRouterFunctions(node, entryPoints);
                return super.visit(node);
            }
        });
        return entryPoints;
    }

    private void processRouterFunctions(TypeDeclaration td, List<EntryPoint> entryPoints) {
        for (MethodDeclaration method : td.getMethods()) {
            if (isBeanMethod(method) && method.getReturnType2() != null && method.getReturnType2().toString().contains("RouterFunction")) {
                method.accept(new ASTVisitor() {
                    @Override
                    public boolean visit(MethodInvocation call) {
                        String name = call.getName().getIdentifier();
                        String verb = getHttpVerb(name);
                        if (verb != null && !call.arguments().isEmpty()) {
                            Expression pathArg = (Expression) call.arguments().get(0);
                            String path = context.resolveExpression(pathArg);

                            entryPoints.add(EntryPoint.builder()
                                    .type(EntryPoint.Type.REST)
                                    .name(verb + " " + path)
                                    .className(context.getFqn(td))
                                    .methodName(method.getName().getIdentifier())
                                    .sourceFile(context.getRelativePath(context.getFqn(td)))
                                    .metadata(Map.of("path", path, "verb", verb, "style", "WebFlux Functional"))
                                    .build());
                        }
                        return super.visit(call);
                    }
                });
            }
        }
    }

    private boolean isBeanMethod(MethodDeclaration method) {
        for (Object mod : method.modifiers()) {
            if (mod instanceof Annotation ann && ann.getTypeName().getFullyQualifiedName().endsWith("Bean")) {
                return true;
            }
        }
        return false;
    }

    private boolean isRestController(TypeDeclaration node) {
        for (Object modifier : node.modifiers()) {
            if (modifier instanceof Annotation annotation) {
                String name = annotation.getTypeName().getFullyQualifiedName();
                if (name.endsWith("RestController") || name.endsWith("Controller") || name.endsWith("Path") || name.endsWith("RequestMapping")) {
                    return true;
                }
            }
        }
        return false;
    }

    private void processController(TypeDeclaration controller, List<EntryPoint> entryPoints) {
        String basePath = getMappingPathHierarchical(controller);
        if (basePath.isEmpty()) {
            basePath = getJaxRsPath(controller);
        }

        for (MethodDeclaration method : controller.getMethods()) {
            EntryPoint ep = processMethodHierarchical(method, basePath, controller);
            if (ep != null) {
                entryPoints.add(ep);
            }
        }
    }

    private String getMappingPathHierarchical(TypeDeclaration td) {
        String path = getMappingPath(td);
        if (!path.isEmpty()) return path;

        for (Object interfaceType : td.superInterfaceTypes()) {
            TypeDeclaration itd = context.getTypeDeclaration(interfaceType.toString());
            if (itd != null) {
                path = getMappingPath(itd);
                if (!path.isEmpty()) return path;
            }
        }

        if (td.getSuperclassType() != null) {
            TypeDeclaration std = context.getTypeDeclaration(td.getSuperclassType().toString());
            if (std != null) {
                return getMappingPathHierarchical(std);
            }
        }

        return "";
    }

    private EntryPoint processMethodHierarchical(MethodDeclaration method, String basePath, TypeDeclaration controller) {
        EntryPoint ep = processMethod(method, basePath, controller);
        if (ep != null) return ep;

        String methodName = method.getName().getIdentifier();
        for (Object interfaceType : controller.superInterfaceTypes()) {
            TypeDeclaration itd = context.getTypeDeclaration(interfaceType.toString());
            if (itd != null) {
                for (MethodDeclaration im : itd.getMethods()) {
                    if (im.getName().getIdentifier().equals(methodName)) {
                        ep = processMethod(im, basePath, controller);
                        if (ep != null) return ep;
                    }
                }
            }
        }
        
        return null;
    }

    private EntryPoint processMethod(MethodDeclaration method, String basePath, TypeDeclaration controller) {
        for (Object modifier : method.modifiers()) {
            if (modifier instanceof Annotation annotation) {
                String name = annotation.getTypeName().getFullyQualifiedName();
                String verb = getHttpVerb(name);
                if (verb != null) {
                    String methodPath = getMappingPath(annotation);
                    if (methodPath.isEmpty()) {
                        methodPath = getJaxRsPath(method);
                    }
                    String fullPath = combinePaths(basePath, methodPath);
                    
                    Map<String, String> metadata = new HashMap<>();
                    metadata.put("path", fullPath);
                    metadata.put("verb", verb);

                    List<EntryPoint.Parameter> parameters = extractParameters(method);

                    return EntryPoint.builder()
                            .type(EntryPoint.Type.REST)
                            .name(verb + " " + fullPath)
                            .className(context.getFqn(controller))
                            .methodName(method.getName().getIdentifier())
                            .sourceFile(context.getRelativePath(context.getFqn(controller)))
                            .metadata(metadata)
                            .parameters(parameters)
                            .build();
                }
            }
        }
        return null;
    }

    private String getJaxRsPath(BodyDeclaration node) {
        for (Object mod : node.modifiers()) {
            if (mod instanceof Annotation ann && ann.getTypeName().getFullyQualifiedName().endsWith("Path")) {
                return getMappingPath(ann);
            }
        }
        return "";
    }

    private String getHttpVerb(String annotationName) {
        if (annotationName.endsWith("PostMapping") || annotationName.endsWith("POST") || "POST".equals(annotationName)) return "POST";
        if (annotationName.endsWith("GetMapping") || annotationName.endsWith("GET") || "GET".equals(annotationName)) return "GET";
        if (annotationName.endsWith("PutMapping") || annotationName.endsWith("PUT") || "PUT".equals(annotationName)) return "PUT";
        if (annotationName.endsWith("DeleteMapping") || annotationName.endsWith("DELETE") || "DELETE".equals(annotationName)) return "DELETE";
        if (annotationName.endsWith("PatchMapping") || annotationName.endsWith("PATCH") || "PATCH".equals(annotationName)) return "PATCH";
        if (annotationName.endsWith("RequestMapping")) return "REQUEST";
        return null;
    }

    private String getMappingPath(TypeDeclaration td) {
        for (Object modifier : td.modifiers()) {
            if (modifier instanceof Annotation annotation) {
                String name = annotation.getTypeName().getFullyQualifiedName();
                if (name.endsWith("RequestMapping") || name.endsWith("Path")) {
                    return getMappingPath(annotation);
                }
            }
        }
        return "";
    }

    private String getMappingPath(Annotation annotation) {
        Expression value = null;
        if (annotation instanceof SingleMemberAnnotation sma) {
            value = sma.getValue();
        } else if (annotation instanceof NormalAnnotation na) {
            for (Object pairObj : na.values()) {
                MemberValuePair pair = (MemberValuePair) pairObj;
                if ("value".equals(pair.getName().getIdentifier()) || "path".equals(pair.getName().getIdentifier())) {
                    value = pair.getValue();
                    break;
                }
            }
        }
        
        if (value != null) {
            return context.resolveExpression(value);
        }
        return "";
    }

    private List<EntryPoint.Parameter> extractParameters(MethodDeclaration method) {
        List<EntryPoint.Parameter> parameters = new ArrayList<>();
        for (Object paramObj : method.parameters()) {
            if (paramObj instanceof SingleVariableDeclaration param) {
                List<String> annotations = new ArrayList<>();
                for (Object mod : param.modifiers()) {
                    if (mod instanceof Annotation ann) {
                        String name = ann.getTypeName().getFullyQualifiedName();
                        if (name.endsWith("PathParam")) name = "PathVariable";
                        if (name.endsWith("QueryParam")) name = "RequestParam";
                        annotations.add(name);
                    }
                }
                parameters.add(EntryPoint.Parameter.builder()
                        .name(param.getName().getIdentifier())
                        .type(param.getType().toString())
                        .annotations(annotations)
                        .build());
            }
        }
        return parameters;
    }

    private String combinePaths(String base, String method) {
        String b = base.startsWith("/") ? base : "/" + base;
        if (b.endsWith("/")) b = b.substring(0, b.length() - 1);
        String m = method.startsWith("/") ? method : "/" + method;
        if ("/".equals(m)) m = "";
        return b + m;
    }
}
