package click.kamil.springstatemachineexporter.analysis.resolver;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Shared boolean simplification for transition guards and dispatcher domain checks.
 */
public final class BooleanConstraintEvaluator {

    private static final Pattern STRING_LITERAL_PATTERN = Pattern.compile("[\"']([a-zA-Z0-9_-]+)[\"']");

    private BooleanConstraintEvaluator() {
    }

    /**
     * Simplifies constraints such as {@code "ORDER".equals(domain)} against a machine domain key.
     */
    public static boolean isCompatibleWithMachineDomain(String constraint, String machineDomainKey) {
        if (constraint == null || machineDomainKey == null) {
            return true;
        }

        String cleanMachine = machineDomainKey.toUpperCase();
        String expr = constraint;
        Set<String> literals = extractStringLiterals(constraint);

        for (String literal : literals) {
            boolean matchesMachine = literal.equalsIgnoreCase(cleanMachine)
                    || cleanMachine.equalsIgnoreCase(literal)
                    || (cleanMachine.length() > literal.length() && cleanMachine.startsWith(literal.toUpperCase()));
            String replacement = matchesMachine ? "true" : "false";

            String regex1 = "(?i)[\"']?" + Pattern.quote(literal) + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\([^)]*\\)";
            String regex2 = "(?i)[a-zA-Z0-9._]+\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?"
                    + Pattern.quote(literal) + "[\"']?\\s*\\)";
            String regex3 = "(?i)[a-zA-Z0-9._]+\\s*==\\s*[\"']?" + Pattern.quote(literal) + "[\"']?";
            String regex4 = "(?i)[a-zA-Z0-9._]+\\s*!=\\s*[\"']?" + Pattern.quote(literal) + "[\"']?";

            expr = expr.replaceAll(regex1, replacement);
            expr = expr.replaceAll(regex2, replacement);
            expr = expr.replaceAll(regex3, replacement);
            expr = expr.replaceAll(regex4, matchesMachine ? "false" : "true");
        }

        return evaluateBooleanExpression(expr);
    }

    public static boolean evaluateBooleanExpression(String expression) {
        if (expression == null || expression.isBlank()) {
            return true;
        }
        try {
            return parseExpression(expression.replaceAll("\\s+", ""));
        } catch (Exception e) {
            // Be conservative in pruning: if we can't parse the expression, do not discard paths.
            return !expression.contains("false");
        }
    }

    /**
     * Evaluates a constraint against known variable bindings gathered while walking a call path forward.
     * Supports {@code var == VALUE} where VALUE may be a quoted string or enum constant; suffix matching
     * is used so {@code command == ORDER_PAY} matches {@code DomainCommand.ORDER_PAY}.
     */
    public static boolean isCompatibleWithBindings(String constraint, Map<String, String> bindings) {
        if (constraint == null || constraint.isBlank()) {
            return true;
        }
        if (bindings == null || bindings.isEmpty()) {
            return true;
        }
        Set<String> equalityVars = extractEqualityVariables(constraint);
        for (String varName : equalityVars) {
            if (!bindings.containsKey(varName)) {
                return false;
            }
        }
        String expr = constraint;
        for (Map.Entry<String, String> entry : bindings.entrySet()) {
            if (!isConcreteBindingValue(entry.getKey(), entry.getValue())) {
                continue;
            }
            expr = substituteVariableBindings(expr, entry.getKey(), entry.getValue());
            expr = substituteEqualsLiteralBindings(expr, entry.getKey(), entry.getValue());
        }
        return evaluateBooleanExpression(expr);
    }

    private static boolean isConcreteBindingValue(String paramName, String boundValue) {
        if (boundValue == null || boundValue.isBlank() || boundValue.equals(paramName)) {
            return false;
        }
        if (boundValue.contains("(") && !boundValue.contains("valueOf")) {
            return false;
        }
        if (boundValue.startsWith("\"") && boundValue.endsWith("\"")) {
            return true;
        }
        if ("true".equalsIgnoreCase(boundValue) || "false".equalsIgnoreCase(boundValue)) {
            return true;
        }
        if (boundValue.contains(".") && Character.isUpperCase(boundValue.charAt(boundValue.lastIndexOf('.') + 1))) {
            return true;
        }
        // Treat simple routing keys (e.g. order.pay) as concrete string bindings, but avoid
        // mistaking variable names (e.g. machineType) for concrete values.
        if ((boundValue.contains(".") || boundValue.contains("/") || boundValue.contains("-"))
                && boundValue.matches("^[a-zA-Z0-9._/-]+$")) {
            return true;
        }
        return boundValue.equals(boundValue.toUpperCase(Locale.ROOT)) && boundValue.chars().allMatch(ch ->
                Character.isUpperCase(ch) || ch == '_');
    }

    private static String substituteEqualsLiteralBindings(String expr, String varName, String boundValue) {
        if (boundValue == null || boundValue.isBlank()) {
            return expr;
        }
        String cleanValue = boundValue;
        if (cleanValue.startsWith("\"") && cleanValue.endsWith("\"")) {
            cleanValue = cleanValue.substring(1, cleanValue.length() - 1);
        }
        String literalPattern = Pattern.quote(cleanValue);
        String receiverEquals = "(?i)[\"']?" + literalPattern + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*"
                + Pattern.quote(varName) + "\\s*\\)";
        String argumentEquals = "(?i)" + Pattern.quote(varName) + "\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?"
                + literalPattern + "[\"']?\\s*\\)";
        expr = expr.replaceAll(receiverEquals, "true");
        expr = expr.replaceAll(argumentEquals, "true");

        Set<String> allLiterals = extractStringLiterals(expr);
        for (String literal : allLiterals) {
            if (literal.equalsIgnoreCase(cleanValue)) {
                continue;
            }
            String otherLiteral = Pattern.quote(literal);
            String otherReceiver = "(?i)[\"']?" + otherLiteral + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*"
                    + Pattern.quote(varName) + "\\s*\\)";
            String otherArgument = "(?i)" + Pattern.quote(varName) + "\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?"
                    + otherLiteral + "[\"']?\\s*\\)";
            expr = expr.replaceAll(otherReceiver, "false");
            expr = expr.replaceAll(otherArgument, "false");
        }
        return expr;
    }

    private static Set<String> extractEqualityVariables(String constraint) {
        Set<String> vars = new HashSet<>();
        Pattern pattern = Pattern.compile("([a-zA-Z][\\w]*)\\s*==");
        Matcher matcher = pattern.matcher(constraint);
        while (matcher.find()) {
            vars.add(matcher.group(1));
        }
        return vars;
    }

    private static String substituteVariableBindings(String expr, String varName, String boundValue) {
        if (boundValue == null || boundValue.isBlank()) {
            return expr;
        }
        String cleanValue = boundValue;
        if (cleanValue.startsWith("\"") && cleanValue.endsWith("\"")) {
            cleanValue = cleanValue.substring(1, cleanValue.length() - 1);
        }
        String suffix = cleanValue.contains(".") ? cleanValue.substring(cleanValue.lastIndexOf('.') + 1) : cleanValue;

        Pattern eqPattern = Pattern.compile(
                "(?i)" + Pattern.quote(varName) + "\\s*==\\s*([\\w.\"']+)");
        Matcher matcher = eqPattern.matcher(expr);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String rhs = matcher.group(1).replace("\"", "").replace("'", "");
            boolean matches = cleanValue.equals(rhs)
                    || cleanValue.endsWith("." + rhs)
                    || suffix.equalsIgnoreCase(rhs)
                    || cleanValue.equalsIgnoreCase(rhs);
            matcher.appendReplacement(sb, matches ? "true" : "false");
        }
        matcher.appendTail(sb);
        if ("true".equalsIgnoreCase(cleanValue) || "false".equalsIgnoreCase(cleanValue)) {
            Pattern bareVar = Pattern.compile("(?<![\\w.])" + Pattern.quote(varName) + "(?![\\w])");
            return bareVar.matcher(expr).replaceAll(cleanValue.toLowerCase(Locale.ROOT));
        }
        return sb.toString();
    }

    /**
     * Returns {@code true}/{@code false} when both sides are compile-time string literals; otherwise {@code null}.
     */
    public static Boolean evaluateStringEquals(String leftLiteral, String rightLiteral, boolean ignoreCase) {
        if (leftLiteral == null || rightLiteral == null) {
            return null;
        }
        return ignoreCase
                ? leftLiteral.equalsIgnoreCase(rightLiteral)
                : leftLiteral.equals(rightLiteral);
    }

    private static Set<String> extractStringLiterals(String constraint) {
        Set<String> literals = new HashSet<>();
        Matcher matcher = STRING_LITERAL_PATTERN.matcher(constraint);
        while (matcher.find()) {
            literals.add(matcher.group(1));
        }
        return literals;
    }

    private static boolean parseExpression(String expression) {
        if (expression.isEmpty()) {
            return true;
        }

        int parenDepth = 0;
        for (int i = expression.length() - 1; i >= 0; i--) {
            char c = expression.charAt(i);
            if (c == ')') {
                parenDepth++;
            } else if (c == '(') {
                parenDepth--;
            } else if (parenDepth == 0 && i > 0 && expression.charAt(i) == '|' && expression.charAt(i - 1) == '|') {
                return parseExpression(expression.substring(0, i - 1)) || parseExpression(expression.substring(i + 1));
            }
        }

        parenDepth = 0;
        for (int i = expression.length() - 1; i >= 0; i--) {
            char c = expression.charAt(i);
            if (c == ')') {
                parenDepth++;
            } else if (c == '(') {
                parenDepth--;
            } else if (parenDepth == 0 && i > 0 && expression.charAt(i) == '&' && expression.charAt(i - 1) == '&') {
                return parseExpression(expression.substring(0, i - 1)) && parseExpression(expression.substring(i + 1));
            }
        }

        if (expression.startsWith("!")) {
            return !parseExpression(expression.substring(1));
        }
        if (expression.startsWith("(") && expression.endsWith(")")) {
            return parseExpression(expression.substring(1, expression.length() - 1));
        }
        if ("true".equalsIgnoreCase(expression)) {
            return true;
        }
        if ("false".equalsIgnoreCase(expression)) {
            return false;
        }
        return true;
    }
}
