package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.LibraryHint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
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.Collections;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
public class GenericEventDetector {
    private final CodebaseContext context;
    private final ConstantResolver constantResolver;
    private final List<LibraryHint> hints;

    public List<TriggerPoint> detect(CompilationUnit cu) {
        List<TriggerPoint> triggers = new ArrayList<>();
        String fileName = cu.getJavaElement() != null ? cu.getJavaElement().getElementName() : "unknown";
        log.debug("Scanning for triggers in {}", fileName);
        
        cu.accept(new ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                String methodName = node.getName().getIdentifier();
                
                // 1. Core sendEvent
                if ("sendEvent".equals(methodName)) {
                    processSendEvent(node, cu, triggers);
                } 
                
                // 2. Library Hints
                processHints(node, cu, triggers);

                return super.visit(node);
            }
        });
        return triggers;
    }

    private void processSendEvent(MethodInvocation node, CompilationUnit cu, List<TriggerPoint> triggers) {
        log.info("Found potential trigger: {}", node);
        TriggerPoint trigger = buildTriggerPoint(node, cu, null);
        if (trigger != null) {
            log.info("Successfully built trigger point: {}", trigger.getEvent());
            triggers.add(trigger);
        }
    }

    private void processHints(MethodInvocation node, CompilationUnit cu, List<TriggerPoint> triggers) {
        if (hints == null || hints.isEmpty()) return;

        String calledMethod = resolveCalledMethodName(node);
        if (calledMethod == null) return;
        
        log.info("Checking method call '{}' against {} hints", calledMethod, hints.size());

        for (LibraryHint hint : hints) {
            if (calledMethod.equals(hint.getMethodFqn()) || isHintMatch(calledMethod, hint.getMethodFqn())) {
                log.info("MATCH! Found hint for '{}'", calledMethod);
                TriggerPoint trigger = buildTriggerPoint(node, cu, hint.getEvent());
                if (trigger != null) {
                    log.info("Successfully built synthetic trigger point from hint: {}", trigger.getEvent());
                    triggers.add(trigger);
                }
            }
        }
    }

    private boolean isHintMatch(String called, String hintFqn) {
        return hintFqn.endsWith("." + called);
    }

    private String resolveCalledMethodName(MethodInvocation node) {
        Expression receiver = node.getExpression();
        String methodName = node.getName().getIdentifier();
        if (receiver == null) return methodName;

        if (receiver instanceof SimpleName sn) {
            String name = sn.getIdentifier();
            // Check if it's a field in the enclosing class
            TypeDeclaration td = findEnclosingType(node);
            if (td != null) {
                for (FieldDeclaration fd : td.getFields()) {
                    for (Object fragObj : fd.fragments()) {
                        if (fragObj instanceof VariableDeclarationFragment fragment) {
                            if (fragment.getName().getIdentifier().equals(name)) {
                                String typeName = fd.getType().toString();
                                return typeName + "." + methodName;
                            }
                        }
                    }
                }
            }
            return name + "." + methodName;
        }
        
        return receiver.toString() + "." + methodName;
    }

    private TriggerPoint buildTriggerPoint(MethodInvocation node, CompilationUnit cu, String forcedEvent) {
        String eventValue;
        if (forcedEvent != null) {
            eventValue = forcedEvent;
        } else {
            if (node.arguments().isEmpty()) return null;
            Expression eventExpr = (Expression) node.arguments().get(0);
            eventValue = constantResolver.resolve(eventExpr, context);
            if (eventValue == null) {
                eventValue = eventExpr.toString(); 
            }
        }

        MethodDeclaration method = findEnclosingMethod(node);
        TypeDeclaration type = findEnclosingType(node);

        if (type == null) return null;

        return TriggerPoint.builder()
                .event(eventValue)
                .className(context.getFqn(type))
                .methodName(method != null ? method.getName().getIdentifier() : "initializer")
                .sourceFile(context.getRelativePath(context.getFqn(type)))
                .lineNumber(cu.getLineNumber(node.getStartPosition()))
                .build();
    }

    private MethodDeclaration findEnclosingMethod(ASTNode node) {
        ASTNode parent = node.getParent();
        while (parent != null && !(parent instanceof MethodDeclaration)) {
            parent = parent.getParent();
        }
        return (MethodDeclaration) parent;
    }

    private TypeDeclaration findEnclosingType(ASTNode node) {
        ASTNode parent = node.getParent();
        while (parent != null && !(parent instanceof TypeDeclaration)) {
            parent = parent.getParent();
        }
        return (TypeDeclaration) parent;
    }
}
