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