package click.kamil.springstatemachineexporter.analysis.enricher.matching;

import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.model.Event;
import java.util.List;

public class StrictFqnMatchingEngine implements EventMatchingEngine {

    private final CodebaseContext context;

    public StrictFqnMatchingEngine() {
        this(null);
    }

    public StrictFqnMatchingEngine(CodebaseContext context) {
        this.context = context;
    }

    @Override
    public boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint) {
        if (stateMachineEvent == null || triggerPoint == null || triggerPoint.getEvent() == null) {
            return false;
        }

        if (click.kamil.springstatemachineexporter.analysis.model.LifecycleTriggerMarkers
                .isLifecycle(triggerPoint.getEvent())) {
            return false;
        }

        String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
        String rawTriggerEvent = triggerPoint.getEvent();

        if (triggerPoint.isExternal()) {
            List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null
                    ? triggerPoint.getPolymorphicEvents()
                    : java.util.Collections.emptyList();
            if (polyEvents.isEmpty() && isDynamicVariable(rawTriggerEvent)) {
                return false;
            }
        }

        if (rawTriggerEvent != null && rawTriggerEvent.startsWith("<SYMBOLIC: ") && rawTriggerEvent.endsWith(".*>")) {
            return false;
        }

        List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
        
        for (String pe : polyEvents) {
            if (pe.startsWith("<SYMBOLIC: ") && pe.endsWith(".*>")) {
                continue;
            }
            String eventTypeFqn = triggerPoint.getEventTypeFqn();
            if (pe.contains(".") && MachineEnumCanonicalizer.isStringOrPrimitiveEventType(eventTypeFqn)) {
                eventTypeFqn = null;
            }
            if (matchEventNames(pe, smEventRaw, eventTypeFqn)) {
                return true;
            }
            if (!pe.contains(".") && smEventRaw.contains(".")) {
                String smConst = constantName(smEventRaw);
                if (pe.equals(smConst) && triggerPoint.getEventTypeFqn() != null
                        && !MachineEnumCanonicalizer.isStringOrPrimitiveEventType(triggerPoint.getEventTypeFqn())) {
                    String smEnumType = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
                    return typesMatch(triggerPoint.getEventTypeFqn(), smEnumType);
                }
            }
        }

        if (rawTriggerEvent != null && rawTriggerEvent.contains(".valueOf(")) {
            return false;
        }

        if (polyEvents.isEmpty() && isDynamicVariable(rawTriggerEvent)) {
            if (triggerPoint.getEventTypeFqn() != null && !MachineEnumCanonicalizer.isStringOrPrimitiveEventType(triggerPoint.getEventTypeFqn())) {
                return false;
            }
            // Getter/method-call expressions (e.g. richEvent.getId(), getType()) without resolved
            // polymorphic events must not wildcard-match bare or enum transition events.
            if (rawTriggerEvent.contains(".") || rawTriggerEvent.endsWith("()")) {
                return false;
            }
            if (triggerPoint.getEventTypeFqn() != null && MachineEnumCanonicalizer.isStringOrPrimitiveEventType(triggerPoint.getEventTypeFqn())) {
                return !smEventRaw.contains(".");
            }
            return !smEventRaw.contains(".");
        }

        return matchEventNames(rawTriggerEvent, smEventRaw, triggerPoint.getEventTypeFqn());
    }

    private boolean matchEventNames(String triggerEvent, String smEvent, String eventTypeFqn) {
        if (triggerEvent == null || smEvent == null) return false;
        if (triggerEvent.equals(smEvent)) return true;

        String tConst = constantName(triggerEvent);
        String smConst = constantName(smEvent);

        if (!tConst.equals(smConst)) return false;

        // String-typed triggers may match configured string transition events by constant name.
        if (MachineEnumCanonicalizer.isStringOrPrimitiveEventType(eventTypeFqn) && smEvent.contains(".") && !triggerEvent.contains(".")) {
            String smEnumType = smEvent.substring(0, smEvent.lastIndexOf('.'));
            return MachineEnumCanonicalizer.isStringOrPrimitiveEventType(smEnumType);
        }

        // Trigger scraped as String.CONST while config still stores the bare literal constant.
        if (MachineEnumCanonicalizer.isStringOrPrimitiveEventType(eventTypeFqn) && triggerEvent.contains(".") && !smEvent.contains(".")) {
            String triggerEnumType = triggerEvent.substring(0, triggerEvent.lastIndexOf('.'));
            return MachineEnumCanonicalizer.isStringOrPrimitiveEventType(triggerEnumType);
        }

        // Qualified trigger against bare SM event without eventTypeFqn: only when the qualifier
        // itself is a string/primitive type (not a real enum like OrderEvent.PAY ↔ PAY).
        if (eventTypeFqn == null && !smEvent.contains(".") && triggerEvent.contains(".")) {
            String triggerEnumType = triggerEvent.substring(0, triggerEvent.lastIndexOf('.'));
            return MachineEnumCanonicalizer.isStringOrPrimitiveEventType(triggerEnumType);
        }

        if (smEvent.contains(".")) {
            String smEnumType = smEvent.substring(0, smEvent.lastIndexOf('.'));

            if (eventTypeFqn != null) {
                return typesMatch(eventTypeFqn, smEnumType);
            }

            if (triggerEvent.contains(".")) {
                String triggerEnumType = triggerEvent.substring(0, triggerEvent.lastIndexOf('.'));
                return typesMatch(triggerEnumType, smEnumType);
            }

            // Bare constant name (e.g. after Enum.name()) with no type context must not match every enum
            return false;
        }

        // Both sides are unqualified identifiers (string/primitive events)
        return !triggerEvent.contains(".");
    }

    private boolean typesMatch(String type1, String type2) {
        if (MachineEnumCanonicalizer.enumTypesMatch(type1, type2, context)) {
            return true;
        }
        if (type1 != null && type2 != null) {
            String machineFqn = type1.contains(".") ? type1 : type2;
            String candidate = type1.contains(".") ? type2 : type1;
            return MachineEnumCanonicalizer.enumTypesMatchForMachine(machineFqn, candidate, context);
        }
        return false;
    }

    private String constantName(String event) {
        return event.contains(".") ? event.substring(event.lastIndexOf('.') + 1) : event;
    }

    private boolean isDynamicVariable(String eventStr) {
        if (eventStr == null || eventStr.isEmpty()) return false;
        
        if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
            return true;
        }
        
        if (eventStr.endsWith("()")) {
            return true;
        }
        
        if (eventStr.contains(".")) {
            String constantPart = eventStr.substring(eventStr.lastIndexOf('.') + 1);
            if (!constantPart.isEmpty() && Character.isUpperCase(constantPart.charAt(0))) {
                return false;
            }
            String firstPart = eventStr.substring(0, eventStr.indexOf('.'));
            if (!firstPart.isEmpty() && Character.isLowerCase(firstPart.charAt(0))) {
                return true;
            }
            return false;
        }

        if (Character.isLowerCase(eventStr.charAt(0))) {
            return true; 
        }

        return false;
    }
}
