package tech.easyflow.ai.node;

import com.easyagents.flow.core.chain.*;
import com.easyagents.flow.core.chain.repository.InMemoryChainStateRepository;
import org.junit.Assert;
import org.junit.Test;

import java.util.*;

public class ConditionNodeTest {

    @Test
    public void testFirstMatch() {
        ConditionNode node = new ConditionNode();

        ConditionNode.ConditionBranch b1 = visualBranch("branch_a", "分支A",
                visualRule("ctx.score", "gte", "fixed", "80", null));
        ConditionNode.ConditionBranch b2 = visualBranch("branch_b", "分支B",
                visualRule("ctx.score", "gte", "fixed", "60", null));
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");

        node.setBranches(Arrays.asList(b1, b2, def));
        node.setDefaultBranchId(def.getId());
        node.setDefaultBranchLabel(def.getLabel());

        Chain chain = createChain(Map.of("ctx", Map.of("score", 90)));
        Map<String, Object> result = node.execute(chain);

        Assert.assertEquals("branch_a", result.get("matchedBranchId"));
        Assert.assertEquals("分支A", result.get("matchedBranchLabel"));
        Assert.assertEquals(false, result.get("matchedByDefault"));
    }

    @Test
    public void testDefaultBranchWhenNoMatch() {
        ConditionNode node = new ConditionNode();

        ConditionNode.ConditionBranch b1 = visualBranch("branch_a", "分支A",
                visualRule("ctx.level", "eq", "fixed", "vip", null));
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");

        node.setBranches(Arrays.asList(b1, def));
        node.setDefaultBranchId(def.getId());
        node.setDefaultBranchLabel(def.getLabel());

        Chain chain = createChain(Map.of("ctx", Map.of("level", "normal")));
        Map<String, Object> result = node.execute(chain);

        Assert.assertEquals("branch_default", result.get("matchedBranchId"));
        Assert.assertEquals("默认分支", result.get("matchedBranchLabel"));
        Assert.assertEquals(true, result.get("matchedByDefault"));
    }

    @Test
    public void testVisualOperators() {
        ConditionNode node = new ConditionNode();

        ConditionNode.ConditionBranch b1 = visualBranch("branch_hit", "命中分支",
                visualRule("ctx.amount", "gt", "fixed", "100", null),
                visualRule("ctx.tags", "contains", "fixed", "vip", null),
                visualRule("ctx.title", "notContains", "fixed", "blacklist", null),
                visualRule("ctx.emptyText", "isEmpty", "fixed", "", null),
                visualRule("ctx.note", "isNotEmpty", "fixed", "", null));
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");

        node.setBranches(Arrays.asList(b1, def));
        node.setDefaultBranchId(def.getId());
        node.setDefaultBranchLabel(def.getLabel());

        Map<String, Object> ctx = new HashMap<>();
        ctx.put("amount", 120);
        ctx.put("tags", Arrays.asList("vip", "new"));
        ctx.put("title", "white-user");
        ctx.put("emptyText", "");
        ctx.put("note", "ok");

        Chain chain = createChain(Map.of("ctx", ctx));
        Map<String, Object> result = node.execute(chain);
        Assert.assertEquals("branch_hit", result.get("matchedBranchId"));
        Assert.assertEquals(false, result.get("matchedByDefault"));
    }

    @Test
    public void testVisualRuleJoinerOr() {
        ConditionNode node = new ConditionNode();

        ConditionNode.ConditionBranch b1 = visualBranch("branch_or", "OR 分支",
                visualRule("ctx.score", "gt", "fixed", "100", null, "AND"),
                visualRule("ctx.level", "eq", "fixed", "vip", null, "OR"));
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");

        node.setBranches(Arrays.asList(b1, def));
        node.setDefaultBranchId(def.getId());
        node.setDefaultBranchLabel(def.getLabel());

        Chain chain = createChain(Map.of("ctx", Map.of("score", 80, "level", "vip")));
        Map<String, Object> result = node.execute(chain);

        Assert.assertEquals("branch_or", result.get("matchedBranchId"));
        Assert.assertEquals(false, result.get("matchedByDefault"));
    }

    @Test
    public void testExpressionModeAndInvalidExpression() {
        ConditionNode node = new ConditionNode();

        ConditionNode.ConditionBranch b1 = expressionBranch("branch_expr", "表达式分支",
                "{{amount}} >= 100 && {{level}} === 'vip'");
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
        node.setBranches(Arrays.asList(b1, def));
        node.setDefaultBranchId(def.getId());
        node.setDefaultBranchLabel(def.getLabel());

        Chain chain = createChain(Map.of("amount", 120, "level", "vip"));
        Map<String, Object> result = node.execute(chain);
        Assert.assertEquals("branch_expr", result.get("matchedBranchId"));

        ConditionNode invalidNode = new ConditionNode();
        ConditionNode.ConditionBranch bad = expressionBranch("branch_bad", "异常分支", "amount >");
        invalidNode.setBranches(Arrays.asList(bad, def));
        invalidNode.setDefaultBranchId(def.getId());
        invalidNode.setDefaultBranchLabel(def.getLabel());

        ChainException ex = Assert.assertThrows(ChainException.class, () -> invalidNode.execute(chain));
        Assert.assertTrue(ex.getMessage().contains("branch_bad"));
        Assert.assertTrue(ex.getMessage().contains("异常分支"));
    }

    @Test
    public void testExpressionModeWithTemplatePath() {
        ConditionNode node = new ConditionNode();

        ConditionNode.ConditionBranch b1 = expressionBranch(
                "branch_expr_tpl",
                "模板分支",
                "{{ctx.order.amount}} >= 10 && {{ctx.user.level}} === 'vip'"
        );
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
        node.setBranches(Arrays.asList(b1, def));
        node.setDefaultBranchId(def.getId());
        node.setDefaultBranchLabel(def.getLabel());

        Map<String, Object> ctx = new HashMap<>();
        ctx.put("order", Map.of("amount", 18));
        ctx.put("user", Map.of("level", "vip"));
        Chain chain = createChain(Map.of("ctx", ctx));

        Map<String, Object> result = node.execute(chain);
        Assert.assertEquals("branch_expr_tpl", result.get("matchedBranchId"));
        Assert.assertEquals(false, result.get("matchedByDefault"));
    }

    /**
     * 验证正则默认查找匹配，并支持锚点和内联大小写标志。
     */
    @Test
    public void testRegexMatchFindAnchorsAndInlineFlag() {
        ConditionNode node = new ConditionNode();
        ConditionNode.ConditionBranch hit = visualBranch(
                "branch_regex",
                "正则分支",
                visualRule(
                        "ctx.orderCode",
                        ConditionRuleSupport.OPERATOR_REGEX_MATCH,
                        "fixed",
                        "(?i)[a-z]+-\\d+",
                        null));
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
        node.setBranches(Arrays.asList(hit, def));
        node.setDefaultBranchId(def.getId());
        node.setDefaultBranchLabel(def.getLabel());

        Map<String, Object> partialResult = node.execute(
                createChain(Map.of("ctx", Map.of("orderCode", "订单ABC-123完成"))));
        Assert.assertEquals("branch_regex", partialResult.get("matchedBranchId"));

        hit.setRules(Collections.singletonList(
                visualRule(
                        "ctx.orderCode",
                        ConditionRuleSupport.OPERATOR_REGEX_MATCH,
                        "fixed",
                        "(?i)^[a-z]+-\\d+$",
                        null)));
        Map<String, Object> anchoredMiss = node.execute(
                createChain(Map.of("ctx", Map.of("orderCode", "订单ABC-123完成"))));
        Assert.assertEquals("branch_default", anchoredMiss.get("matchedBranchId"));

        Map<String, Object> anchoredHit = node.execute(
                createChain(Map.of("ctx", Map.of("orderCode", "ABC-123"))));
        Assert.assertEquals("branch_regex", anchoredHit.get("matchedBranchId"));
    }

    /**
     * 验证正则支持数字和布尔标量，并覆盖语法和长度边界。
     */
    @Test
    public void testRegexMatchScalarAndValidationBoundaries() {
        Assert.assertTrue(ConditionRuleSupport.matchesRegex(123, "^12\\d$"));
        Assert.assertTrue(ConditionRuleSupport.matchesRegex(true, "^true$"));
        Assert.assertFalse(ConditionRuleSupport.matchesRegex(null, ".*"));
        Assert.assertTrue(
                ConditionRuleSupport.validateRegex(" ")
                        .contains("不能为空"));
        Assert.assertTrue(
                ConditionRuleSupport.validateRegex("(a)\\1")
                        .contains("语法错误"));
        Assert.assertTrue(
                ConditionRuleSupport.validateRegex(
                                "a".repeat(ConditionRuleSupport.MAX_REGEX_LENGTH + 1))
                        .contains(String.valueOf(ConditionRuleSupport.MAX_REGEX_LENGTH)));
    }

    /**
     * 验证正则拒绝集合左值并返回可定位错误。
     */
    @Test
    public void testRegexMatchShouldRejectCollectionValue() {
        ConditionNode node = new ConditionNode();
        ConditionNode.ConditionBranch hit = visualBranch(
                "branch_regex",
                "正则分支",
                visualRule(
                        "ctx.values",
                        ConditionRuleSupport.OPERATOR_REGEX_MATCH,
                        "fixed",
                        "\\d+",
                        null));
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
        node.setBranches(Arrays.asList(hit, def));
        node.setDefaultBranchId(def.getId());

        ChainException exception = Assert.assertThrows(
                ChainException.class,
                () -> node.execute(createChain(
                        Map.of("ctx", Map.of("values", Arrays.asList(1, 2))))));

        Assert.assertTrue(exception.getMessage().contains("正则分支"));
        Assert.assertTrue(exception.getMessage().contains("规则[1]"));
        Assert.assertTrue(exception.getMessage().contains("仅支持字符串、数字或布尔值"));
    }

    /**
     * 验证未知条件操作符不会静默进入默认分支。
     */
    @Test
    public void testUnknownOperatorShouldFailExplicitly() {
        ConditionNode node = new ConditionNode();
        ConditionNode.ConditionBranch hit = visualBranch(
                "branch_invalid",
                "异常分支",
                visualRule("ctx.value", "unknown", "fixed", "x", null));
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
        node.setBranches(Arrays.asList(hit, def));
        node.setDefaultBranchId(def.getId());

        ChainException exception = Assert.assertThrows(
                ChainException.class,
                () -> node.execute(createChain(Map.of("ctx", Map.of("value", "x")))));

        Assert.assertTrue(exception.getMessage().contains("不支持的条件操作符"));
        Assert.assertTrue(exception.getMessage().contains("异常分支"));
    }

    @Test
    public void testManagedEdgeConditionRouting() {
        ConditionNode node = new ConditionNode();

        ConditionNode.ConditionBranch b1 = visualBranch("branch_yes", "通过",
                visualRule("ctx.allow", "eq", "fixed", "true", null));
        ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
        node.setBranches(Arrays.asList(b1, def));
        node.setDefaultBranchId(def.getId());
        node.setDefaultBranchLabel(def.getLabel());

        Chain chain = createChain(Map.of("ctx", Map.of("allow", true)));
        Map<String, Object> result = node.execute(chain);

        Edge yesEdge = new Edge("e_yes");
        yesEdge.setCondition(new JsCodeCondition("matchedBranchId === 'branch_yes'"));
        Assert.assertTrue(yesEdge.getCondition().check(chain, yesEdge, result));

        Edge noEdge = new Edge("e_no");
        noEdge.setCondition(new JsCodeCondition("matchedBranchId === 'branch_other'"));
        Assert.assertFalse(noEdge.getCondition().check(chain, noEdge, result));
    }

    private static Chain createChain(Map<String, Object> memory) {
        Chain chain = new Chain(new ChainDefinition(), UUID.randomUUID().toString());
        chain.setChainStateRepository(new InMemoryChainStateRepository());
        if (memory != null && !memory.isEmpty()) {
            chain.getState().getMemory().putAll(memory);
        }
        return chain;
    }

    private static ConditionNode.ConditionBranch defaultBranch(String id, String label) {
        ConditionNode.ConditionBranch branch = new ConditionNode.ConditionBranch();
        branch.setId(id);
        branch.setLabel(label);
        branch.setMode("visual");
        branch.setRules(Collections.emptyList());
        return branch;
    }

    private static ConditionNode.ConditionBranch expressionBranch(String id, String label, String expression) {
        ConditionNode.ConditionBranch branch = new ConditionNode.ConditionBranch();
        branch.setId(id);
        branch.setLabel(label);
        branch.setMode("expression");
        branch.setExpression(expression);
        branch.setRules(Collections.emptyList());
        return branch;
    }

    private static ConditionNode.ConditionBranch visualBranch(
            String id, String label, ConditionNode.ConditionRule... rules) {
        ConditionNode.ConditionBranch branch = new ConditionNode.ConditionBranch();
        branch.setId(id);
        branch.setLabel(label);
        branch.setMode("visual");
        branch.setRules(Arrays.asList(rules));
        return branch;
    }

    private static ConditionNode.ConditionRule visualRule(
            String leftRef, String operator, String rightType, String rightValue, String rightRef) {
        return visualRule(leftRef, operator, rightType, rightValue, rightRef, "AND");
    }

    private static ConditionNode.ConditionRule visualRule(
            String leftRef, String operator, String rightType, String rightValue, String rightRef, String joiner) {
        ConditionNode.ConditionRule rule = new ConditionNode.ConditionRule();
        rule.setId(UUID.randomUUID().toString());
        rule.setJoiner(joiner);
        rule.setLeftRef(leftRef);
        rule.setOperator(operator);
        rule.setRightType(rightType);
        rule.setRightValue(rightValue);
        rule.setRightRef(rightRef);
        return rule;
    }
}
