package eino

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"github.com/cloudwego/eino/schema"

	"github.com/sundynix/sundynix-dispatcher/internal/dsl"
	"github.com/sundynix/sundynix-dispatcher/internal/harness"
	"github.com/sundynix/sundynix-dispatcher/internal/llm"
)

// TestParseSpecialists 钉死节点 config 的 agents 列表解析（含 tools 子集、无名跳过）。
func TestParseSpecialists(t *testing.T) {
	cfg := map[string]any{"agents": []any{
		map[string]any{"name": "legal", "use": "法律条款", "system": "你是律师", "tools": []any{"wiki_search", "kb_search"}},
		map[string]any{"name": "finance", "use": "财务测算"},
		map[string]any{"use": "无名字应被跳过"},
	}}
	specs := parseSpecialists(cfg)
	if len(specs) != 2 {
		t.Fatalf("应解析出 2 个专家（无名跳过），got %d", len(specs))
	}
	if specs[0].Name != "legal" || specs[0].Use != "法律条款" || len(specs[0].Tools) != 2 {
		t.Fatalf("legal 解析有误: %+v", specs[0])
	}
	if specs[1].Name != "finance" || len(specs[1].Tools) != 0 {
		t.Fatalf("finance 解析有误: %+v", specs[1])
	}
}

// TestSpecialistToolInvoke 钉死 agent-as-tool 包装：lead 写的 brief 透传给专家、结论回传、
// 空 brief 兜底、专家失败作为"观察"返回而非中断协调。
func TestSpecialistToolInvoke(t *testing.T) {
	var gotBrief string
	st := &specialistTool{
		name: "legal", tr: &execTracer{},
		info: &schema.ToolInfo{Name: "legal"},
		run:  func(_ context.Context, brief string) (string, error) { gotBrief = brief; return "结论：条款合规", nil },
	}

	out, err := st.InvokableRun(context.Background(), `{"brief":"审查合同第3条违约金"}`)
	if err != nil {
		t.Fatal(err)
	}
	if gotBrief != "审查合同第3条违约金" {
		t.Fatalf("lead 简报应透传给专家，got %q", gotBrief)
	}
	if out != "结论：条款合规" {
		t.Fatalf("专家结论应回传，got %q", out)
	}

	// 空 brief → 兜底文案（不把空串丢给专家）。
	st.run = func(_ context.Context, brief string) (string, error) { gotBrief = brief; return "ok", nil }
	_, _ = st.InvokableRun(context.Background(), `{}`)
	if !strings.Contains(gotBrief, "未给简报") {
		t.Fatalf("空 brief 应兜底，got %q", gotBrief)
	}

	// 专家失败 → 返回失败观察，err 为 nil（不中断 orchestrator 的 ReAct 循环）。
	st.run = func(_ context.Context, _ string) (string, error) { return "", fmt.Errorf("boom") }
	out, err = st.InvokableRun(context.Background(), `{"brief":"x"}`)
	if err != nil {
		t.Fatalf("专家失败不应上抛 error（应作观察返回）: %v", err)
	}
	if !strings.Contains(out, "执行失败") {
		t.Fatalf("应返回失败观察，got %q", out)
	}
}

// TestRunCoordinatorDegrade 钉死降级：模型不支持函数调用（ToolCallingModel=nil）→ 退回普通对话出稿，
// 不因"多智能体"而卡死或空答。
func TestRunCoordinatorDegrade(t *testing.T) {
	fs := &fakeSink{}
	o := &Orchestrator{
		pool:    &fakeLLM{ready: true, stream: func([]llm.ChatMessage) string { return "降级直答" }},
		breaker: harness.NewCircuitBreaker(),
		sink:    fs,
	}
	b := &board{query: "你好"}
	n := dsl.Node{ID: "c", Kind: "coordinator", Config: map[string]any{
		"agents": []any{map[string]any{"name": "x", "use": "y"}},
	}}
	o.runCoordinator(context.Background(), "t_co", b, "", n, &execTracer{}, "coordinator:c")
	if !strings.Contains(b.answer, "降级直答") {
		t.Fatalf("ToolCallingModel 缺失应降级 runAgent 出答案，got %q", b.answer)
	}
}
