package workflow

import (
	"context"
	"testing"
)

func TestEvalCondition_extendedOperators(t *testing.T) {
	state := newWorkflowLocalState(map[string]interface{}{"score": 9, "message": "status: ok"}, "run-expr")
	state.LastOutput = map[string]any{"output": "asset-123.example.com"}

	tests := []string{
		"{{inputs.score}} >= 9",
		"{{inputs.message}} contains ok",
		"{{previous.output}} matches ^asset-[0-9]+\\.example\\.com$",
		"{{inputs.score}} > 5 && {{inputs.message}} contains status",
	}
	for _, expr := range tests {
		if err := validateConditionExpression(expr); err != nil {
			t.Fatalf("validate %q: %v", expr, err)
		}
		if !evalCondition(expr, state) {
			t.Fatalf("evalCondition(%q) = false, want true", expr)
		}
	}
}

func TestEvalCondition_jsonPathAndJQSafeSubset(t *testing.T) {
	state := newWorkflowLocalState(map[string]interface{}{
		"payload": map[string]any{
			"risk": 9,
			"items": []any{
				map[string]any{"name": "first"},
			},
		},
	}, "run-jsonpath")
	state.LastOutput = map[string]any{"output": `{"status":"ok","score":7}`}

	tests := []string{
		`jsonpath({{inputs.payload}}, "$.risk") >= 8`,
		`jq({{inputs.payload}}, ".items[0].name") == first`,
		`jsonpath({{previous.output}}, "$.status") == ok`,
	}
	for _, expr := range tests {
		if err := validateConditionExpression(expr); err != nil {
			t.Fatalf("validate %q: %v", expr, err)
		}
		if !evalCondition(expr, state) {
			t.Fatalf("evalCondition(%q) = false, want true", expr)
		}
	}
}

func TestMergeUpstreamOutputs_allMerge(t *testing.T) {
	got := mergeUpstreamOutputs(JoinAllMerge, []map[string]any{
		{"output": "a", "left": 1},
		{"output": "b", "right": 2},
	})
	if got["kind"] != "join" || got["strategy"] != JoinAllMerge {
		t.Fatalf("join metadata = %#v", got)
	}
	values, ok := got["output"].([]any)
	if !ok || len(values) != 2 || values[0] != "a" || values[1] != "b" {
		t.Fatalf("merged output = %#v", got["output"])
	}
	if got["left"] != 1 || got["right"] != 2 {
		t.Fatalf("merged fields = %#v", got)
	}
}

func TestMergeUpstreamOutputs_firstNonEmpty(t *testing.T) {
	got := mergeUpstreamOutputs(JoinFirstNonEmpty, []map[string]any{
		{"output": ""},
		{"output": "winner"},
	})
	if got["output"] != "winner" {
		t.Fatalf("output = %#v, want winner", got["output"])
	}
}

func TestDryRunGraphJSON_simulatesUnsafeNodes(t *testing.T) {
	graph := `{
  "nodes": [
    {"id": "start-1", "type": "start", "label": "开始", "position": {"x": 0, "y": 0}, "config": {}},
    {"id": "agent-1", "type": "agent", "label": "Agent", "position": {"x": 0, "y": 80}, "config": {"instruction": "noop", "output_key": "agent_result"}},
    {"id": "out-1", "type": "output", "label": "输出", "position": {"x": 0, "y": 160}, "config": {"output_key": "result", "source_binding": {"from": "outputs", "field": "agent_result"}}}
  ],
  "edges": [
    {"id": "e1", "source": "start-1", "target": "agent-1"},
    {"id": "e2", "source": "agent-1", "target": "out-1"}
  ]
}`
	result, err := DryRunGraphJSON(nilContext(), graph, map[string]any{"message": "hello"})
	if err != nil {
		t.Fatalf("DryRunGraphJSON: %v", err)
	}
	if got := result.Outputs["result"]; got != "[dry-run] agent execution skipped" {
		t.Fatalf("result output = %#v", got)
	}
	if len(result.Trace) != 3 {
		t.Fatalf("trace len = %d, want 3", len(result.Trace))
	}
}

func nilContext() context.Context {
	return context.Background()
}
