package tech.easyflow.ai.node;

import com.alibaba.fastjson.JSON;
import com.easyagents.flow.core.chain.Chain;
import com.easyagents.flow.core.chain.ChainException;
import com.easyagents.flow.core.node.BaseNode;
import com.easyagents.flow.core.util.JsConditionUtil;
import com.easyagents.flow.core.util.StringUtil;

import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 条件判断节点：首个命中（if / else-if）语义。
 */
public class ConditionNode extends BaseNode {
    private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\{\\{\\s*([^{}]+?)\\s*}}");

    private String branchMode = "first_match";
    private String defaultBranchId;
    private String defaultBranchLabel;
    private List<ConditionBranch> branches = new ArrayList<>();

    @Override
    public Map<String, Object> execute(Chain chain) {
        if (branches == null || branches.isEmpty()) {
            throw new ChainException("条件判断节点未配置任何分支");
        }

        ConditionBranch defaultBranch = resolveDefaultBranch();

        for (ConditionBranch branch : branches) {
            if (branch == null) {
                continue;
            }
            if (defaultBranch != null && Objects.equals(defaultBranch.getId(), branch.getId())) {
                continue;
            }
            if (isBranchMatched(chain, branch)) {
                return buildMatchedResult(branch, false);
            }
        }

        if (defaultBranch == null) {
            throw new ChainException("条件判断节点缺少默认分支，请检查配置");
        }

        return buildMatchedResult(defaultBranch, true);
    }

    private ConditionBranch resolveDefaultBranch() {
        if (branches == null || branches.isEmpty()) {
            return null;
        }

        if (StringUtil.hasText(defaultBranchId)) {
            for (ConditionBranch branch : branches) {
                if (branch != null && Objects.equals(defaultBranchId, branch.getId())) {
                    return branch;
                }
            }
        }

        if (StringUtil.hasText(defaultBranchLabel)) {
            for (ConditionBranch branch : branches) {
                if (branch != null && Objects.equals(defaultBranchLabel, branch.getLabel())) {
                    return branch;
                }
            }
        }

        return branches.get(branches.size() - 1);
    }

    private Map<String, Object> buildMatchedResult(ConditionBranch branch, boolean matchedByDefault) {
        Map<String, Object> result = new HashMap<>();
        result.put("matchedBranchId", branch.getId());
        result.put("matchedBranchLabel", branch.getLabel());
        result.put("matchedByDefault", matchedByDefault);
        return result;
    }

    private boolean isBranchMatched(Chain chain, ConditionBranch branch) {
        if (branch == null) {
            return false;
        }

        String mode = StringUtil.getFirstWithText(branch.getMode(), "visual");
        if ("expression".equalsIgnoreCase(mode)) {
            return checkByExpression(chain, branch);
        }

        return checkByVisualRules(chain, branch);
    }

    private boolean checkByExpression(Chain chain, ConditionBranch branch) {
        String expression = branch.getExpression();
        if (StringUtil.noText(expression)) {
            return false;
        }

        try {
            String resolvedExpression = resolveExpressionTemplate(expression, chain);
            return JsConditionUtil.eval(resolvedExpression, chain, new HashMap<>());
        } catch (Exception e) {
            throw new ChainException(String.format("条件分支表达式执行失败，分支[%s/%s]: %s",
                    branch.getId(), branch.getLabel(), e.getMessage()), e);
        }
    }

    private String resolveExpressionTemplate(String expression, Chain chain) {
        Matcher matcher = TEMPLATE_PARAM_PATTERN.matcher(expression);
        StringBuffer output = new StringBuffer();

        while (matcher.find()) {
            String path = matcher.group(1) == null ? "" : matcher.group(1).trim();
            Object value = StringUtil.noText(path) ? null : chain.getState().resolveValue(path);
            matcher.appendReplacement(output, Matcher.quoteReplacement(toJsLiteral(value)));
        }
        matcher.appendTail(output);
        return output.toString();
    }

    private String toJsLiteral(Object value) {
        if (value == null) {
            return "null";
        }
        if (value instanceof Number || value instanceof Boolean) {
            return String.valueOf(value);
        }
        if (value instanceof Character || value.getClass().isEnum()) {
            return JSON.toJSONString(String.valueOf(value));
        }
        return JSON.toJSONString(value);
    }

    private boolean checkByVisualRules(Chain chain, ConditionBranch branch) {
        List<ConditionRule> rules = branch.getRules();
        if (rules == null || rules.isEmpty()) {
            return false;
        }

        Boolean matched = null;
        for (ConditionRule rule : rules) {
            boolean ruleMatched = checkRule(chain, rule);
            if (matched == null) {
                matched = ruleMatched;
                continue;
            }

            String joiner = StringUtil.getFirstWithText(rule.getJoiner(), "AND");
            if ("OR".equalsIgnoreCase(joiner)) {
                matched = matched || ruleMatched;
            } else {
                matched = matched && ruleMatched;
            }
        }

        return Boolean.TRUE.equals(matched);
    }

    private boolean checkRule(Chain chain, ConditionRule rule) {
        if (rule == null || StringUtil.noText(rule.getOperator())) {
            return false;
        }

        Object leftValue = resolveValue(chain, rule.getLeftRef());
        String operator = rule.getOperator();

        if ("isEmpty".equals(operator)) {
            return isEmpty(leftValue);
        }

        if ("isNotEmpty".equals(operator)) {
            return !isEmpty(leftValue);
        }

        Object rightValue = resolveRightValue(chain, rule);

        switch (operator) {
            case "eq":
                return equalsSmart(leftValue, rightValue);
            case "ne":
                return !equalsSmart(leftValue, rightValue);
            case "gt":
                return compareNumber(leftValue, rightValue, it -> it > 0);
            case "gte":
                return compareNumber(leftValue, rightValue, it -> it >= 0);
            case "lt":
                return compareNumber(leftValue, rightValue, it -> it < 0);
            case "lte":
                return compareNumber(leftValue, rightValue, it -> it <= 0);
            case "contains":
                return contains(leftValue, rightValue);
            case "notContains":
                return !contains(leftValue, rightValue);
            default:
                return false;
        }
    }

    private Object resolveRightValue(Chain chain, ConditionRule rule) {
        if (rule == null) {
            return null;
        }

        if ("ref".equals(rule.getRightType())) {
            return resolveValue(chain, rule.getRightRef());
        }

        return rule.getRightValue();
    }

    private Object resolveValue(Chain chain, String path) {
        if (chain == null || StringUtil.noText(path)) {
            return null;
        }

        return chain.getState().resolveValue(path);
    }

    private boolean isEmpty(Object value) {
        if (value == null) {
            return true;
        }

        if (value instanceof CharSequence) {
            return StringUtil.noText(value.toString());
        }

        if (value instanceof Collection) {
            return ((Collection<?>) value).isEmpty();
        }

        if (value instanceof Map) {
            return ((Map<?, ?>) value).isEmpty();
        }

        if (value.getClass().isArray()) {
            return java.lang.reflect.Array.getLength(value) == 0;
        }

        return false;
    }

    private boolean contains(Object leftValue, Object rightValue) {
        if (leftValue == null) {
            return false;
        }

        if (leftValue instanceof String) {
            return rightValue != null && ((String) leftValue).contains(String.valueOf(rightValue));
        }

        if (leftValue instanceof Collection) {
            Collection<?> collection = (Collection<?>) leftValue;
            for (Object item : collection) {
                if (equalsSmart(item, rightValue)) {
                    return true;
                }
            }
            return false;
        }

        if (leftValue.getClass().isArray()) {
            int length = java.lang.reflect.Array.getLength(leftValue);
            for (int i = 0; i < length; i++) {
                Object item = java.lang.reflect.Array.get(leftValue, i);
                if (equalsSmart(item, rightValue)) {
                    return true;
                }
            }
            return false;
        }

        return false;
    }

    private boolean compareNumber(Object leftValue, Object rightValue, java.util.function.IntPredicate compareFunc) {
        BigDecimal leftNumber = toBigDecimal(leftValue);
        BigDecimal rightNumber = toBigDecimal(rightValue);

        if (leftNumber == null || rightNumber == null) {
            return false;
        }

        return compareFunc.test(leftNumber.compareTo(rightNumber));
    }

    private boolean equalsSmart(Object leftValue, Object rightValue) {
        if (leftValue == null || rightValue == null) {
            return leftValue == rightValue;
        }

        BigDecimal leftNumber = toBigDecimal(leftValue);
        BigDecimal rightNumber = toBigDecimal(rightValue);
        if (leftNumber != null && rightNumber != null) {
            return leftNumber.compareTo(rightNumber) == 0;
        }

        if (leftValue instanceof Boolean || rightValue instanceof Boolean) {
            return toBoolean(leftValue) == toBoolean(rightValue);
        }

        return Objects.equals(String.valueOf(leftValue), String.valueOf(rightValue));
    }

    private boolean toBoolean(Object value) {
        if (value == null) {
            return false;
        }

        if (value instanceof Boolean) {
            return (Boolean) value;
        }

        String stringValue = String.valueOf(value).trim();
        if (StringUtil.noText(stringValue)) {
            return false;
        }

        return "true".equalsIgnoreCase(stringValue)
                || "1".equals(stringValue)
                || "yes".equalsIgnoreCase(stringValue)
                || "y".equalsIgnoreCase(stringValue);
    }

    private BigDecimal toBigDecimal(Object value) {
        if (value == null) {
            return null;
        }

        if (value instanceof Number) {
            return new BigDecimal(String.valueOf(value));
        }

        String text = String.valueOf(value).trim();
        if (StringUtil.noText(text)) {
            return null;
        }

        try {
            return new BigDecimal(text);
        } catch (Exception e) {
            return null;
        }
    }

    public String getBranchMode() {
        return branchMode;
    }

    public void setBranchMode(String branchMode) {
        this.branchMode = branchMode;
    }

    public String getDefaultBranchId() {
        return defaultBranchId;
    }

    public void setDefaultBranchId(String defaultBranchId) {
        this.defaultBranchId = defaultBranchId;
    }

    public String getDefaultBranchLabel() {
        return defaultBranchLabel;
    }

    public void setDefaultBranchLabel(String defaultBranchLabel) {
        this.defaultBranchLabel = defaultBranchLabel;
    }

    public List<ConditionBranch> getBranches() {
        return branches;
    }

    public void setBranches(List<ConditionBranch> branches) {
        this.branches = branches;
    }

    public static class ConditionBranch {
        private String id;
        private String label;
        private String mode;
        private String expression;
        private List<ConditionRule> rules = new ArrayList<>();

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getLabel() {
            return label;
        }

        public void setLabel(String label) {
            this.label = label;
        }

        public String getMode() {
            return mode;
        }

        public void setMode(String mode) {
            this.mode = mode;
        }

        public String getExpression() {
            return expression;
        }

        public void setExpression(String expression) {
            this.expression = expression;
        }

        public List<ConditionRule> getRules() {
            return rules;
        }

        public void setRules(List<ConditionRule> rules) {
            this.rules = rules;
        }
    }

    public static class ConditionRule {
        private String id;
        private String joiner;
        private String leftRef;
        private String operator;
        private String rightType;
        private String rightValue;
        private String rightRef;

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getJoiner() {
            return joiner;
        }

        public void setJoiner(String joiner) {
            this.joiner = joiner;
        }

        public String getLeftRef() {
            return leftRef;
        }

        public void setLeftRef(String leftRef) {
            this.leftRef = leftRef;
        }

        public String getOperator() {
            return operator;
        }

        public void setOperator(String operator) {
            this.operator = operator;
        }

        public String getRightType() {
            return rightType;
        }

        public void setRightType(String rightType) {
            this.rightType = rightType;
        }

        public String getRightValue() {
            return rightValue;
        }

        public void setRightValue(String rightValue) {
            this.rightValue = rightValue;
        }

        public String getRightRef() {
            return rightRef;
        }

        public void setRightRef(String rightRef) {
            this.rightRef = rightRef;
        }
    }
}
