package click.kamil.springstatemachineexporter.analysis.enricher;

import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.service.CodebaseIntelligenceProvider;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.model.State;
import click.kamil.springstatemachineexporter.model.Transition;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import click.kamil.springstatemachineexporter.exporter.ExportOptions;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.matching.EventMatchingEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine;

public class TransitionLinkerEnricher implements AnalysisEnricher {

    private static final ExportOptions LINK_FORMAT_OPTIONS = ExportOptions.builder().build();

    private final BeanResolutionEngine routingEngine = new HeuristicBeanResolutionEngine();
    private final EventMatchingEngine matchingEngine = new StrictFqnMatchingEngine();

    @Override
    public void enrich(AnalysisResult result, CodebaseContext context, CodebaseIntelligenceProvider intelligence) {
        if (result.getMetadata() == null || result.getMetadata().getCallChains() == null) {
            return;
        }

        List<CallChain> updatedChains = new ArrayList<>();
        List<Transition> stateMachineTransitions = result.getTransitions();

        for (CallChain chain : result.getMetadata().getCallChains()) {
            TriggerPoint tp = chain.getTriggerPoint();
            if (tp == null || tp.getEvent() == null) {
                updatedChains.add(chain);
                continue;
            }

            String triggerEvent = simplify(tp.getEvent());
            String triggerSource = tp.getSourceState() != null ? simplify(tp.getSourceState()) : null;

            List<MatchedTransition> matched = new ArrayList<>();

            for (Transition t : stateMachineTransitions) {
                if (t.getEvent() != null && matchingEngine.matches(t.getEvent(), tp)) {
                    String smEventForLink = formatEventForLink(t.getEvent());
                    for (State smSourceState : t.getSourceStates()) {
                            String smSourceForLink = formatSourceForLink(smSourceState);
                            String smSource = simplify(smSourceForLink);
                            if (triggerSource == null || triggerSource.equals(smSource)) {
                                if (t.getTargetStates() == null || t.getTargetStates().isEmpty()) {
                                    MatchedTransition mt = MatchedTransition.builder()
                                            .sourceState(smSourceForLink)
                                            .targetState(smSourceForLink)
                                            .event(smEventForLink)
                                            .build();
                                    if (isRoutedToCorrectMachine(chain, result.getName(), context)
                                            && isConstraintCompatible(tp.getConstraint(), result.getName())) {
                                        matched.add(mt);
                                    }
                                } else {
                                    for (State smTargetState : t.getTargetStates()) {
                                        String targetForLink = formatSourceForLink(smTargetState);
                                        MatchedTransition mt = MatchedTransition.builder()
                                                .sourceState(smSourceForLink)
                                                .targetState(targetForLink)
                                                .event(smEventForLink)
                                                .build();
                                        if (isRoutedToCorrectMachine(chain, result.getName(), context)
                                                && isConstraintCompatible(tp.getConstraint(), result.getName())) {
                                            matched.add(mt);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

            if (!matched.isEmpty()) {
                CallChain newChain = chain.toBuilder().matchedTransitions(matched).build();
                updatedChains.add(newChain);
            } else {
                updatedChains.add(chain);
            }
        }

        // Update the metadata with the new call chains
        CodebaseMetadata updatedMetadata = CodebaseMetadata.builder()
                .triggers(result.getMetadata().getTriggers())
                .entryPoints(result.getMetadata().getEntryPoints())
                .callChains(updatedChains)
                .properties(result.getMetadata().getProperties())
                .build();

        result.setMetadata(updatedMetadata);
    }

    
    private boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName, CodebaseContext context) {
        return routingEngine.isRoutedToCorrectMachine(chain, currentMachineName, context);
    }

    private boolean isConstraintCompatible(String constraint, String machineName) {
        if (constraint == null || machineName == null) return true;
        
        String cleanMachine = machineName.substring(machineName.lastIndexOf('.') + 1).toUpperCase();
        if (cleanMachine.endsWith("STATEMACHINECONFIGURATION")) {
            cleanMachine = cleanMachine.substring(0, cleanMachine.length() - "STATEMACHINECONFIGURATION".length());
        }
        if (cleanMachine.endsWith("STATEMACHINE")) {
            cleanMachine = cleanMachine.substring(0, cleanMachine.length() - "STATEMACHINE".length());
        }
        if (cleanMachine.endsWith("CONFIG")) {
            cleanMachine = cleanMachine.substring(0, cleanMachine.length() - "CONFIG".length());
        }
        
        String expr = constraint;
        java.util.regex.Pattern p = java.util.regex.Pattern.compile("[\"']([a-zA-Z0-9_-]+)[\"']");
        java.util.regex.Matcher matcher = p.matcher(constraint);
        java.util.Set<String> literals = new java.util.HashSet<>();
        while (matcher.find()) {
            literals.add(matcher.group(1));
        }
        
        for (String m : literals) {
            boolean isCurrent = m.equalsIgnoreCase(cleanMachine);
            String replacement = isCurrent ? "true" : "false";
            
            String regex1 = "(?i)[\"']?" + m + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\([^)]*\\)";
            String regex2 = "(?i)[a-zA-Z0-9._]+\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?" + m + "[\"']?\\s*\\)";
            String regex3 = "(?i)[a-zA-Z0-9._]+\\s*==\\s*[\"']?" + m + "[\"']?";
            String regex4 = "(?i)[a-zA-Z0-9._]+\\s*!=\\s*[\"']?" + m + "[\"']?";
            
            expr = expr.replaceAll(regex1, replacement);
            expr = expr.replaceAll(regex2, replacement);
            expr = expr.replaceAll(regex3, replacement);
            expr = expr.replaceAll(regex4, isCurrent ? "false" : "true");
        }
        
        try {
            return BooleanEvaluator.evaluate(expr);
        } catch (Exception e) {
            return !expr.contains("false");
        }
    }

    private static class BooleanEvaluator {
        public static boolean evaluate(String expr) {
            expr = expr.replaceAll("\\s+", "");
            return parseExpr(expr);
        }

        private static boolean parseExpr(String s) {
            if (s.isEmpty()) return true;
            
            int parenDepth = 0;
            for (int i = s.length() - 1; i >= 0; i--) {
                char c = s.charAt(i);
                if (c == ')') parenDepth++;
                else if (c == '(') parenDepth--;
                else if (parenDepth == 0 && i > 0 && s.charAt(i) == '|' && s.charAt(i - 1) == '|') {
                    return parseExpr(s.substring(0, i - 1)) || parseExpr(s.substring(i + 1));
                }
            }
            
            parenDepth = 0;
            for (int i = s.length() - 1; i >= 0; i--) {
                char c = s.charAt(i);
                if (c == ')') parenDepth++;
                else if (c == '(') parenDepth--;
                else if (parenDepth == 0 && i > 0 && s.charAt(i) == '&' && s.charAt(i - 1) == '&') {
                    return parseExpr(s.substring(0, i - 1)) && parseExpr(s.substring(i + 1));
                }
            }

            if (s.startsWith("!")) {
                return !parseExpr(s.substring(1));
            }

            if (s.startsWith("(") && s.endsWith(")")) {
                return parseExpr(s.substring(1, s.length() - 1));
            }

            if (s.equalsIgnoreCase("true")) return true;
            if (s.equalsIgnoreCase("false")) return false;
            
            return true;
        }
    }

    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 String simplify(String name) {
        if (name == null) return null;
        if (simplifyCache.containsKey(name)) return simplifyCache.get(name);
        
        // Strip common suffixes
        String simplified = SUFFIX_PATTERN.matcher(name).replaceAll("$1");
        if (simplified.isEmpty()) {
            simplified = name;
        }
        // Simplify full identifiers to just the last part (enum name)
        simplified = FQN_PATTERN.matcher(simplified).replaceAll("$1");
        
        simplifyCache.put(name, simplified);
        return simplified;
    }

    private String formatSourceForLink(State state) {
        if (state == null) return null;
        return simplifyForLink(LINK_FORMAT_OPTIONS.formatState(state));
    }

    private String formatEventForLink(click.kamil.springstatemachineexporter.model.Event event) {
        if (event == null) return null;
        return LINK_FORMAT_OPTIONS.formatEvent(event);
    }

    private String simplifyForLink(String name) {
        if (name == null) return "";
        if (name.contains("\"")) return name;
        return name.replaceAll("[^a-zA-Z0-9_.]", "");
    }

    private boolean isGuardedContains(String smEvent, String triggerEvent) {
        if (smEvent == null || triggerEvent == null) return false;
        
        // Use a strictly immutable pair to guarantee 100% collision-free caching regardless of string content
        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 shorter = smEvent.length() < triggerEvent.length() ? smEvent : triggerEvent;
        String longer = smEvent.length() >= triggerEvent.length() ? smEvent : triggerEvent;
        
        shorter = shorter.toLowerCase();
        longer = longer.toLowerCase();
        
        if (longer.contains(shorter)) {
            // Guard: If the matching part is very short (< 4 chars), it must be bounded by word separators
            // to avoid matching completely unrelated words (e.g., "PAY" matching "PAYMENT" or "E" matching "UPDATE")
            if (shorter.length() < 4) {
                return longer.matches(".*(^|_|-)" + java.util.regex.Pattern.quote(shorter) + "(_|-|$).*");
            }
            // For longer strings, a straight contains is usually safe enough
            return true;
        }
        return false;
    }

}