package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import lombok.RequiredArgsConstructor;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.TypeDeclaration;

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

@RequiredArgsConstructor
public class SpringComponentDetector {
    private final CodebaseContext context;

    public List<EntryPoint> detect(CompilationUnit cu) {
        List<EntryPoint> entryPoints = new ArrayList<>();
        cu.accept(new ASTVisitor() {
            @Override
            public boolean visit(MethodDeclaration node) {
                if (!(node.getParent() instanceof TypeDeclaration)) {
                    return super.visit(node);
                }
                TypeDeclaration parentTd = (TypeDeclaration) node.getParent();

                for (Object modifier : node.modifiers()) {
                    if (modifier instanceof Annotation annotation) {
                        String typeName = annotation.getTypeName().getFullyQualifiedName();
                        if (typeName.endsWith("Scheduled")) {
                            Map<String, String> meta = new HashMap<>();
                            String cron = extractAnnotationValue(annotation, "cron");
                            if (!cron.isEmpty())
                                meta.put("cron", cron);
                            String fixedRate = extractAnnotationValue(annotation, "fixedRate");
                            if (!fixedRate.isEmpty())
                                meta.put("fixedRate", fixedRate);
                            String fixedDelay = extractAnnotationValue(annotation, "fixedDelay");
                            if (!fixedDelay.isEmpty())
                                meta.put("fixedDelay", fixedDelay);

                            entryPoints.add(EntryPoint.builder()
                                    .type(EntryPoint.Type.CUSTOM)
                                    .name("@Scheduled: " + node.getName().getIdentifier())
                                    .className(context.getFqn(parentTd))
                                    .methodName(node.getName().getIdentifier())
                                    .sourceFile(context.getRelativePath(context.getFqn(parentTd)))
                                    .metadata(meta)
                                    .build());
                        } else if (typeName.endsWith("EventListener")) {
                            Map<String, String> meta = new HashMap<>();
                            String classes = extractAnnotationValue(annotation, "classes");
                            if (!classes.isEmpty())
                                meta.put("classes", classes);
                            String condition = extractAnnotationValue(annotation, "condition");
                            if (!condition.isEmpty())
                                meta.put("condition", condition);

                            entryPoints.add(EntryPoint.builder()
                                    .type(EntryPoint.Type.CUSTOM)
                                    .name("@EventListener: " + node.getName().getIdentifier())
                                    .className(context.getFqn(parentTd))
                                    .methodName(node.getName().getIdentifier())
                                    .sourceFile(context.getRelativePath(context.getFqn(parentTd)))
                                    .metadata(meta)
                                    .build());
                        } else if (typeName.endsWith("Around") || typeName.endsWith("Before") || typeName.endsWith("After") || typeName.endsWith("AfterReturning") || typeName.endsWith("AfterThrowing")) {
                            Map<String, String> meta = new HashMap<>();
                            String value = extractAnnotationValue(annotation, "value");
                            if (!value.isEmpty())
                                meta.put("pointcut", value);
                            else {
                                String pointcut = extractAnnotationValue(annotation, "pointcut");
                                if (!pointcut.isEmpty())
                                    meta.put("pointcut", pointcut);
                            }

                            entryPoints.add(EntryPoint.builder()
                                    .type(EntryPoint.Type.CUSTOM)
                                    .name("@" + annotation.getTypeName().getFullyQualifiedName() + ": " + node.getName().getIdentifier())
                                    .className(context.getFqn(parentTd))
                                    .methodName(node.getName().getIdentifier())
                                    .sourceFile(context.getRelativePath(context.getFqn(parentTd)))
                                    .metadata(meta)
                                    .build());
                        }
                    }
                }
                return super.visit(node);
            }
        });
        return entryPoints;
    }

    private String extractAnnotationValue(Annotation annotation, String memberName) {
        if (annotation instanceof SingleMemberAnnotation sma) {
            if ("value".equals(memberName)) {
                return sma.getValue().toString();
            }
        } else if (annotation instanceof NormalAnnotation na) {
            for (Object pairObj : na.values()) {
                MemberValuePair pair = (MemberValuePair) pairObj;
                if (pair.getName().getIdentifier().equals(memberName)) {
                    return pair.getValue().toString();
                }
            }
        }
        return "";
    }
}
