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

import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions;
import click.kamil.springstatemachineexporter.model.Event;
import java.util.List;

public class HeuristicEventMatchingEngine implements EventMatchingEngine {

    private final java.util.Map<java.util.Map.Entry<Event, TriggerPoint>, Boolean> matchesCache = new java.util.HashMap<>();

    @Override
    public boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint) {
        if (stateMachineEvent == null || triggerPoint == null || triggerPoint.getEvent() == null) {
            return false;
        }
        java.util.Map.Entry<Event, TriggerPoint> cacheKey = new java.util.AbstractMap.SimpleImmutableEntry<>(stateMachineEvent, triggerPoint);
        if (matchesCache.containsKey(cacheKey)) return matchesCache.get(cacheKey);

        boolean result = calculateMatches(stateMachineEvent, triggerPoint);
        matchesCache.put(cacheKey, result);
        return result;
    }

    private boolean calculateMatches(Event stateMachineEvent, TriggerPoint triggerPoint) {

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

        boolean isWildcard = DynamicTriggerExpressions.isDynamic(rawTriggerEvent);

        List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
        
        boolean hasPolyMatch = false;
        for (String pe : polyEvents) {
            if (pe.contains(".") && smEventRaw.contains(".")) {
                if (smEventRaw.equals(pe) || smEventRaw.endsWith("." + pe) || pe.endsWith("." + smEventRaw)) {
                    hasPolyMatch = true;
                    break;
                }
                String classPe = pe.substring(0, pe.lastIndexOf('.'));
                String classSm = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
                if (classPe.contains(".") && classSm.contains(".")) {
                    String p1 = classPe.substring(0, classPe.lastIndexOf('.'));
                    String p2 = classSm.substring(0, classSm.lastIndexOf('.'));
                    if (!p1.equals(p2) && !p1.startsWith(p2 + ".") && !p2.startsWith(p1 + ".")) {
                        continue;
                    }
                }
                String simpleClassPe = classPe.contains(".") ? classPe.substring(classPe.lastIndexOf('.') + 1) : classPe;
                String simpleClassSm = classSm.contains(".") ? classSm.substring(classSm.lastIndexOf('.') + 1) : classSm;
                if (!simpleClassPe.equals(simpleClassSm)) {
                    continue;
                }
            }

            String simplePe = pe;
            if (pe.contains(".")) {
                simplePe = pe.substring(pe.lastIndexOf('.') + 1);
            }
            String simplifiedPe = simplify(simplePe);

            if (simplePe.equals(smEventRaw) || simplePe.equals(smEvent) || 
                simplePe.equalsIgnoreCase(smEvent) || simplifiedPe.equalsIgnoreCase(smEvent) ||
                isGuardedContains(smEvent, simplifiedPe)) {
                hasPolyMatch = true;
                break;
            }
        }

        if (hasPolyMatch) return true;
        if (polyEvents.isEmpty() && isWildcard) {
            String eventTypeFqn = triggerPoint.getEventTypeFqn();
            if (eventTypeFqn != null && smEventRaw.contains(".")) {
                String smEventClass = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
                if (smEventClass.equals(eventTypeFqn)) {
                    return true;
                }
                String simpleEventType = eventTypeFqn.contains(".") ? eventTypeFqn.substring(eventTypeFqn.lastIndexOf('.') + 1) : eventTypeFqn;
                String simpleSmClass = smEventClass.contains(".") ? smEventClass.substring(smEventClass.lastIndexOf('.') + 1) : smEventClass;
                if (simpleEventType.equalsIgnoreCase(simpleSmClass)) {
                    return true;
                }
                return false;
            }
            return true;
        }

        if (rawTriggerEvent.contains(".") && smEventRaw.contains(".")) {
            if (smEventRaw.equals(rawTriggerEvent) || smEventRaw.endsWith("." + rawTriggerEvent) || rawTriggerEvent.endsWith("." + smEventRaw)) {
                return true;
            }
            String classTrig = rawTriggerEvent.substring(0, rawTriggerEvent.lastIndexOf('.'));
            String classSm = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
            if (classTrig.contains(".") && classSm.contains(".")) {
                String p1 = classTrig.substring(0, classTrig.lastIndexOf('.'));
                String p2 = classSm.substring(0, classSm.lastIndexOf('.'));
                if (!p1.equals(p2) && !p1.startsWith(p2 + ".") && !p2.startsWith(p1 + ".")) {
                    return false;
                }
            }
            String simpleClassTrig = classTrig.contains(".") ? classTrig.substring(classTrig.lastIndexOf('.') + 1) : classTrig;
            String simpleClassSm = classSm.contains(".") ? classSm.substring(classSm.lastIndexOf('.') + 1) : classSm;
            if (!simpleClassTrig.equals(simpleClassSm)) {
                return false;
            }
        }

        String triggerEvent = simplify(rawTriggerEvent);
        if (smEvent.equalsIgnoreCase(triggerEvent)) {
            return true;
        }
        return isGuardedContains(smEvent, triggerEvent);
    }

    private final java.util.Map<String, String> simplifyCache = new java.util.HashMap<>();
    private final java.util.Map<java.util.Map.Entry<String, String>, Boolean> guardedCache = new java.util.HashMap<>();
    private static final java.util.regex.Pattern SUFFIX_PATTERN = java.util.regex.Pattern.compile("(?i)(.+)(Event|Action|Transition|Command)(s)?$");
    private static final java.util.regex.Pattern FQN_PATTERN = java.util.regex.Pattern.compile("^.*\\.([A-Z0-9_]+)$");

    private boolean isGuardedContains(String smEvent, String triggerEvent) {
        if (smEvent == null || triggerEvent == null) return false;
        
        java.util.Map.Entry<String, String> cacheKey = new java.util.AbstractMap.SimpleImmutableEntry<>(smEvent, triggerEvent);
        if (guardedCache.containsKey(cacheKey)) return guardedCache.get(cacheKey);
        
        boolean result = calculateGuardedContains(smEvent, triggerEvent);
        guardedCache.put(cacheKey, result);
        return result;
    }

    private boolean calculateGuardedContains(String smEvent, String triggerEvent) {
        // Only fire if it looks like a simple identifier (no spaces, no parens)
        // Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
        if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
            triggerEvent.contains(" ") || triggerEvent.contains("(") || triggerEvent.contains(")")) {
            return false;
        }
        
        String smLower = smEvent.toLowerCase();
        String triggerLower = triggerEvent.toLowerCase();
        
        if (smLower.equals(triggerLower)) {
            return true;
        }
        
        // Match if one contains the other as a whole word/segment.
        // We quote the patterns to avoid regex syntax injection issues.
        String pattern1 = "\\b" + java.util.regex.Pattern.quote(smLower) + "\\b";
        String pattern2 = "\\b" + java.util.regex.Pattern.quote(triggerLower) + "\\b";
        
        // Treat underscores and hyphens as word boundary separators
        String smNormalized = smLower.replace('_', ' ').replace('-', ' ');
        String triggerNormalized = triggerLower.replace('_', ' ').replace('-', ' ');
        
        return smNormalized.matches(".*" + pattern2 + ".*") || triggerNormalized.matches(".*" + pattern1 + ".*");
    }

    private String simplify(String name) {
        if (name == null) return null;
        if (simplifyCache.containsKey(name)) return simplifyCache.get(name);
        
        String simplified = SUFFIX_PATTERN.matcher(name).replaceAll("$1");
        if (simplified.isEmpty()) {
            simplified = name;
        }
        simplified = FQN_PATTERN.matcher(simplified).replaceAll("$1");
        
        simplifyCache.put(name, simplified);
        return simplified;
    }
}
