package dsl

import (
	"encoding/json"
	"strings"
	"testing"
)

func TestParseAndAssemble_OK(t *testing.T) {
	raw := json.RawMessage(`{"version":"1","nodes":[{"id":"a","kind":"input"}],"edges":[]}`)
	task, err := ParseAndAssemble(raw)
	if err != nil {
		t.Fatalf("合法 DSL 不应报错: %v", err)
	}
	if !strings.HasPrefix(task.ID, "task_") {
		t.Errorf("任务 ID 应以 task_ 前缀, got %q", task.ID)
	}
	if string(task.Graph) != string(raw) {
		t.Error("Graph 应原样透传 DSL")
	}
	if task.Meta == nil {
		t.Error("Meta 应已初始化（供网关注入身份）")
	}
}

func TestParseAndAssemble_Errors(t *testing.T) {
	if _, err := ParseAndAssemble(json.RawMessage(``)); err == nil {
		t.Error("空 DSL 应报错")
	}
	if _, err := ParseAndAssemble(json.RawMessage(`{bad`)); err == nil {
		t.Error("非法 JSON 应报错")
	}
}

func TestParseAndAssemble_Topology(t *testing.T) {
	ok := func(raw string) {
		t.Helper()
		if _, err := ParseAndAssemble(json.RawMessage(raw)); err != nil {
			t.Errorf("应通过: %s\n  err=%v", raw, err)
		}
	}
	bad := func(raw, wantSub string) {
		t.Helper()
		_, err := ParseAndAssemble(json.RawMessage(raw))
		if err == nil {
			t.Errorf("应被拒: %s", raw)
			return
		}
		if !strings.Contains(err.Error(), wantSub) {
			t.Errorf("错误应含 %q, got %q", wantSub, err.Error())
		}
	}

	// 合法：多节点 + 边引用齐全。
	ok(`{"nodes":[{"id":"a","kind":"input"},{"id":"b","kind":"agent"}],"edges":[{"source":"a","target":"b"}]}`)
	// 宽松放过：空图 / 报告任务的 {topic} / 无 nodes 字段。
	ok(`{"nodes":[],"edges":[]}`)
	ok(`{"topic":"季度报告"}`)
	ok(`{"version":"1"}`)

	// 拒绝：重复 id。
	bad(`{"nodes":[{"id":"a"},{"id":"a"}],"edges":[]}`, "重复")
	// 拒绝：空 id。
	bad(`{"nodes":[{"id":""}],"edges":[]}`, "缺少 id")
	// 拒绝：悬挂边（目标节点不存在）。
	bad(`{"nodes":[{"id":"a"}],"edges":[{"source":"a","target":"ghost"}]}`, "不存在的目标节点")
	// 拒绝：悬挂边（源节点不存在，即便无节点）。
	bad(`{"nodes":[],"edges":[{"source":"x","target":"y"}]}`, "不存在的源节点")
	// 拒绝：边端点为空。
	bad(`{"nodes":[{"id":"a"}],"edges":[{"source":"a","target":""}]}`, "不能为空")
}
