package click.kamil.springstatemachineexporter.analysis.resolver;

import click.kamil.springstatemachineexporter.analysis.service.CarrierConstraintSupport;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;

import java.util.ArrayList;
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;
        }

        // Drop event/payload carrier clauses so they never enter the domain rewrite path.
        constraint = CarrierConstraintSupport.stripNonMachineClauses(constraint);
        if (constraint == null || constraint.isBlank()) {
            return true;
        }

        String cleanMachine = machineDomainKey.toUpperCase(Locale.ROOT);
        List<String> rewritten = new ArrayList<>();
        for (String clause : ConstraintClauses.splitAndClauses(constraint)) {
            rewritten.add(rewriteDomainEqualityClause(clause, cleanMachine));
        }
        if (rewritten.isEmpty()) {
            return true;
        }
        return evaluateBooleanExpression(String.join(" && ", rewritten));
    }

    /**
     * Simple equality clauses collapse via {@link ConstraintClauses}; complex clauses ({@code !},
     * {@code ||}, nested parens) keep a residual literal rewrite so negation/OR stay correct.
     */
    private static String rewriteDomainEqualityClause(String clause, String cleanMachine) {
        if (clause == null || clause.isBlank()) {
            return clause;
        }
        String trimmed = clause.trim();
        if ("true".equalsIgnoreCase(trimmed) || "false".equalsIgnoreCase(trimmed)) {
            return trimmed.toLowerCase(Locale.ROOT);
        }
        boolean complex = trimmed.indexOf('!') >= 0
                || trimmed.contains("||")
                || (trimmed.indexOf('(') >= 0 && !isObjectsEqualsClause(trimmed));
        if (!complex) {
            String literal = ConstraintClauses.quotedLiteral(trimmed);
            if (literal != null && ConstraintClauses.primaryIdent(trimmed) != null) {
                boolean matchesMachine = domainLiteralMatches(literal, cleanMachine);
                boolean inequality = trimmed.contains("!=");
                if (inequality) {
                    return matchesMachine ? "false" : "true";
                }
                return matchesMachine ? "true" : "false";
            }
        }
        return rewriteDomainLiteralsWithRegex(trimmed, cleanMachine);
    }

    private static boolean isObjectsEqualsClause(String clause) {
        return clause.matches("(?is)\\s*(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(.*\\)\\s*");
    }

    private static boolean domainLiteralMatches(String literal, String cleanMachine) {
        return literal.equalsIgnoreCase(cleanMachine)
                || cleanMachine.equalsIgnoreCase(literal)
                || (cleanMachine.length() > literal.length()
                && cleanMachine.startsWith(literal.toUpperCase(Locale.ROOT)));
    }

    private static String rewriteDomainLiteralsWithRegex(String expr, String cleanMachine) {
        Set<String> literals = extractStringLiterals(expr);
        String result = expr;
        for (String literal : literals) {
            boolean matchesMachine = domainLiteralMatches(literal, cleanMachine);
            String replacement = matchesMachine ? "true" : "false";
            String quoted = Pattern.quote(literal);
            result = result.replaceAll(
                    "(?i)[\"']?" + quoted + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\([^)]*\\)",
                    replacement);
            result = result.replaceAll(
                    "(?i)[a-zA-Z0-9._]+\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?"
                            + quoted + "[\"']?\\s*\\)",
                    replacement);
            result = result.replaceAll(
                    "(?i)[a-zA-Z0-9._]+\\s*==\\s*[\"']?" + quoted + "[\"']?",
                    replacement);
            result = result.replaceAll(
                    "(?i)[a-zA-Z0-9._]+\\s*!=\\s*[\"']?" + quoted + "[\"']?",
                    matchesMachine ? "false" : "true");
            result = result.replaceAll(
                    "(?i)(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(\\s*[\"']?"
                            + quoted + "[\"']?\\s*,\\s*[^)]+\\)",
                    replacement);
            result = result.replaceAll(
                    "(?i)(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(\\s*[^,]+\\s*,\\s*[\"']?"
                            + quoted + "[\"']?\\s*\\)",
                    replacement);
        }
        return result;
    }

    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) {
        return isCompatibleWithBindings(constraint, bindings, null, null);
    }

    /**
     * Like {@link #isCompatibleWithBindings(String, Map)} but unbound equality variables are ignored
     * instead of failing closed. Used when filtering call chains against entry path/payload bindings
     * while trigger constraints may also mention unrelated locals.
     */
    public static boolean isCompatibleWithKnownBindings(String constraint, Map<String, String> bindings) {
        return isCompatibleWithKnownBindings(constraint, bindings, null, null);
    }

    /**
     * Like {@link #isCompatibleWithBindings(String, Map)} but reconstructs truncated enum refs
     * using {@code preferredEnumTypeFqn} before treating them as concrete.
     */
    public static boolean isCompatibleWithBindings(
            String constraint,
            Map<String, String> bindings,
            String preferredEnumTypeFqn,
            CodebaseContext context) {
        return isCompatibleWithBindings(constraint, bindings, preferredEnumTypeFqn, context, false);
    }

    public static boolean isCompatibleWithKnownBindings(
            String constraint,
            Map<String, String> bindings,
            String preferredEnumTypeFqn,
            CodebaseContext context) {
        return isCompatibleWithBindings(constraint, bindings, preferredEnumTypeFqn, context, true);
    }

    private static boolean isCompatibleWithBindings(
            String constraint,
            Map<String, String> bindings,
            String preferredEnumTypeFqn,
            CodebaseContext context,
            boolean ignoreUnboundEqualityVars) {
        if (constraint == null || constraint.isBlank()) {
            return true;
        }
        if (bindings == null || bindings.isEmpty()) {
            return true;
        }
        Set<String> equalityVars = ConstraintClauses.equalityVariables(constraint);
        for (String varName : equalityVars) {
            if (!bindings.containsKey(varName)) {
                if (ignoreUnboundEqualityVars) {
                    continue;
                }
                return false;
            }
        }
        String expr = constraint;
        for (Map.Entry<String, String> entry : bindings.entrySet()) {
            String concreteValue = concreteBindingValue(
                    entry.getKey(), entry.getValue(), preferredEnumTypeFqn, context);
            if (concreteValue == null) {
                continue;
            }
            expr = substituteVariableBindings(expr, entry.getKey(), concreteValue);
            expr = substituteEqualsLiteralBindings(expr, entry.getKey(), concreteValue);
        }
        return evaluateBooleanExpression(expr);
    }

    private static boolean isConcreteBindingValue(String paramName, String boundValue) {
        return concreteBindingValue(paramName, boundValue, null, null) != null;
    }

    /**
     * @return concrete value to substitute (possibly reconstructed), or {@code null} if not concrete
     */
    private static String concreteBindingValue(
            String paramName,
            String boundValue,
            String preferredEnumTypeFqn,
            CodebaseContext context) {
        if (boundValue == null || boundValue.isBlank() || boundValue.equals(paramName)) {
            return null;
        }
        if (click.kamil.springstatemachineexporter.ast.common.AstUtils.isAnalysisToken(boundValue)) {
            return null;
        }
        if (isIncompleteDottedName(boundValue)) {
            return null;
        }
        String candidate = boundValue;
        if (TruncatedEnumRefReconstructor.looksLikePackageTruncatedEnumRef(boundValue)) {
            if (context == null) {
                return null;
            }
            candidate = TruncatedEnumRefReconstructor.reconstruct(
                    boundValue, preferredEnumTypeFqn, context);
            if (TruncatedEnumRefReconstructor.looksLikePackageTruncatedEnumRef(candidate)) {
                return null;
            }
        }
        if (candidate.contains("(") && !candidate.contains("valueOf")) {
            return null;
        }
        if (candidate.startsWith("\"") && candidate.endsWith("\"")) {
            return candidate;
        }
        if ("true".equalsIgnoreCase(candidate) || "false".equalsIgnoreCase(candidate)) {
            return candidate;
        }
        int lastDot = candidate.lastIndexOf('.');
        if (lastDot >= 0
                && lastDot + 1 < candidate.length()
                && Character.isUpperCase(candidate.charAt(lastDot + 1))) {
            return candidate;
        }
        // Treat simple routing keys (e.g. order.pay) as concrete string bindings, but avoid
        // mistaking variable names (e.g. machineType) for concrete values.
        if ((candidate.contains(".") || candidate.contains("/") || candidate.contains("-"))
                && candidate.matches("^[a-zA-Z0-9._/-]+$")) {
            return candidate;
        }
        if (candidate.equals(candidate.toUpperCase(Locale.ROOT)) && candidate.chars().allMatch(ch ->
                Character.isUpperCase(ch) || ch == '_')) {
            return candidate;
        }
        return null;
    }

    /**
     * True for truncated/malformed dotted names such as {@code com.example.DomainCommand.} or {@code .PAY}.
     */
    public static boolean isIncompleteDottedName(String value) {
        return value != null
                && !value.isBlank()
                && (value.startsWith(".") || value.endsWith(".") || value.contains(".."));
    }

    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 varToken = Pattern.quote(varName);
        String argumentEquals = "(?i)" + varToken + "\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?"
                + literalPattern + "[\"']?\\s*\\)";
        expr = replaceReceiverEqualsForVariable(expr, varName, cleanValue, "true");
        expr = replaceObjectsEqualsForVariable(expr, varName, cleanValue, "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 otherArgument = "(?i)" + varToken + "\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?"
                    + otherLiteral + "[\"']?\\s*\\)";
            expr = replaceReceiverEqualsForVariable(expr, varName, literal, "false");
            expr = replaceObjectsEqualsForVariable(expr, varName, literal, "false");
            expr = expr.replaceAll(otherArgument, "false");
        }
        return expr;
    }

    private static String replaceObjectsEqualsForVariable(
            String expr, String varName, String literalValue, String replacement) {
        Pattern head = Pattern.compile("(?is)(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(");
        Matcher matcher = head.matcher(expr);
        StringBuilder result = new StringBuilder();
        int cursor = 0;
        while (matcher.find()) {
            int matchStart = matcher.start();
            int openParen = matcher.end() - 1;
            int closeParen = findMatchingCloseParen(expr, openParen);
            if (closeParen < 0) {
                break;
            }
            String[] args = splitTopLevelComma(expr.substring(openParen + 1, closeParen));
            result.append(expr, cursor, matchStart);
            if (args != null
                    && args.length == 2
                    && objectsEqualsArgumentMatches(args[0], args[1], varName, literalValue)) {
                result.append(replacement);
            } else {
                result.append(expr, matchStart, closeParen + 1);
            }
            cursor = closeParen + 1;
        }
        result.append(expr.substring(cursor));
        return result.toString();
    }

    private static boolean objectsEqualsArgumentMatches(
            String left, String right, String varName, String literalValue) {
        String leftLiteral = stripConstraintLiteral(left);
        String rightLiteral = stripConstraintLiteral(right);
        if (leftLiteral != null
                && leftLiteral.equalsIgnoreCase(literalValue)
                && constraintArgumentReferencesVariable(right, varName)) {
            return true;
        }
        return rightLiteral != null
                && rightLiteral.equalsIgnoreCase(literalValue)
                && constraintArgumentReferencesVariable(left, varName);
    }

    private static String stripConstraintLiteral(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        if (trimmed.length() >= 2
                && ((trimmed.startsWith("\"") && trimmed.endsWith("\""))
                || (trimmed.startsWith("'") && trimmed.endsWith("'")))) {
            return trimmed.substring(1, trimmed.length() - 1);
        }
        return null;
    }

    private static boolean constraintArgumentReferencesVariable(String argument, String varName) {
        return argument != null && argument.matches("(?s).*\\b" + Pattern.quote(varName) + "\\b.*");
    }

    private static String[] splitTopLevelComma(String args) {
        int depth = 0;
        for (int i = 0; i < args.length(); i++) {
            char c = args.charAt(i);
            if (c == '(') {
                depth++;
            } else if (c == ')') {
                depth--;
            } else if (c == ',' && depth == 0) {
                return new String[] {args.substring(0, i), args.substring(i + 1)};
            }
        }
        return null;
    }

    private static String replaceReceiverEqualsForVariable(
            String expr, String varName, String literalValue, String replacement) {
        Pattern head = Pattern.compile(
                "(?is)[\"']?" + Pattern.quote(literalValue) + "[\"']?\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(");
        Matcher matcher = head.matcher(expr);
        StringBuilder result = new StringBuilder();
        int cursor = 0;
        while (matcher.find()) {
            int matchStart = matcher.start();
            int openParen = matcher.end() - 1;
            int closeParen = findMatchingCloseParen(expr, openParen);
            if (closeParen < 0) {
                break;
            }
            String argument = expr.substring(openParen + 1, closeParen).trim();
            result.append(expr, cursor, matchStart);
            if (argument.equals(varName)
                    || argument.matches("(?s).*\\b" + Pattern.quote(varName) + "\\b.*")) {
                result.append(replacement);
            } else {
                result.append(expr, matchStart, closeParen + 1);
            }
            cursor = closeParen + 1;
        }
        result.append(expr.substring(cursor));
        return result.toString();
    }

    private static int findMatchingCloseParen(String expr, int openIdx) {
        int depth = 0;
        for (int i = openIdx; i < expr.length(); i++) {
            char c = expr.charAt(i);
            if (c == '(') {
                depth++;
            } else if (c == ')') {
                depth--;
                if (depth == 0) {
                    return i;
                }
            }
        }
        return -1;
    }

    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);
        }

        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 = constraintValuesMatch(cleanValue, 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();
    }

    private static boolean constraintValuesMatch(String boundValue, String rhs) {
        if (boundValue == null || rhs == null) {
            return false;
        }
        String bound = stripOuterQuotes(boundValue);
        String rhsClean = stripOuterQuotes(rhs);
        if (bound.equals(rhsClean) || bound.equalsIgnoreCase(rhsClean)) {
            return true;
        }
        if (bound.endsWith("." + rhsClean) || rhsClean.endsWith("." + bound)) {
            return true;
        }

        String boundConstant = enumConstantName(bound);
        String rhsConstant = enumConstantName(rhsClean);
        if (!boundConstant.equalsIgnoreCase(rhsConstant)) {
            return false;
        }

        String boundType = enumTypePart(bound);
        String rhsType = enumTypePart(rhsClean);
        if (boundType == null || rhsType == null) {
            return true;
        }
        if (boundType.equals(rhsType)) {
            return true;
        }

        String boundSimple = simpleTypeName(boundType);
        String rhsSimple = simpleTypeName(rhsType);
        if (!boundSimple.equals(rhsSimple)) {
            return false;
        }
        boolean boundImportStyle = !boundType.contains(".");
        boolean rhsImportStyle = !rhsType.contains(".");
        return boundImportStyle || rhsImportStyle;
    }

    private static String stripOuterQuotes(String value) {
        if (value == null) {
            return null;
        }
        if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
            return value.substring(1, value.length() - 1);
        }
        return value;
    }

    private static String enumConstantName(String ref) {
        int dot = ref.lastIndexOf('.');
        return dot >= 0 ? ref.substring(dot + 1) : ref;
    }

    private static String enumTypePart(String ref) {
        int dot = ref.lastIndexOf('.');
        if (dot <= 0) {
            return null;
        }
        return ref.substring(0, dot);
    }

    private static String simpleTypeName(String typePart) {
        int dot = typePart.lastIndexOf('.');
        return dot >= 0 ? typePart.substring(dot + 1) : typePart;
    }

    /**
     * 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;
    }
}
