package eino

import (
	"context"
	"errors"
	"strings"
	"testing"

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

// writeSections：真·LLM 失败应逐项标记（Body 带可见失败标记）并汇总失败数，不静默丢失。
func TestWriteSections_PropagatesFailures(t *testing.T) {
	ll := &fakeLLM{ready: true, chat: func(_ []llm.ChatMessage) (string, error) {
		return "", errors.New("boom")
	}}
	o := newOrch(ll, kbTool(nil), &fakeSink{}, &fakeExec{})

	secs, failed := o.writeSections(context.Background(), "主题", "", []string{"A", "B", "C"}, o.tracer("t1"))
	if failed != 3 {
		t.Fatalf("全失败应 failed=3，got %d", failed)
	}
	if len(secs) != 3 {
		t.Fatalf("应返回 3 个 section，got %d", len(secs))
	}
	for _, s := range secs {
		if !strings.Contains(s.Body, "撰写失败") {
			t.Errorf("失败项 Body 应含可见失败标记，got %q", s.Body)
		}
	}
}

// 部分失败：仅命中 heading B 的调用失败 → failed 精确为 1（非 all-or-nothing），A/C 正常。
func TestWriteSections_PartialFailureCount(t *testing.T) {
	ll := &fakeLLM{ready: true, chat: func(m []llm.ChatMessage) (string, error) {
		for _, msg := range m {
			if strings.Contains(msg.Content, "本章标题：B") {
				return "", errors.New("boom-B")
			}
		}
		return "正文", nil
	}}
	o := newOrch(ll, kbTool(nil), &fakeSink{}, &fakeExec{})

	secs, failed := o.writeSections(context.Background(), "主题", "", []string{"A", "B", "C"}, o.tracer("t1"))
	if failed != 1 {
		t.Fatalf("仅 B 失败应 failed=1，got %d", failed)
	}
	if !strings.Contains(secs[1].Body, "撰写失败") {
		t.Errorf("B 应标记失败，got %q", secs[1].Body)
	}
	if strings.Contains(secs[0].Body, "撰写失败") || strings.Contains(secs[2].Body, "撰写失败") {
		t.Errorf("A/C 不应被误判失败")
	}
}

// mapNode：并行 fan-out 全部子项失败 → 置 fatalErr，任务判 failed 而非静默 done-空。
func TestMapNode_AllFailSetsFatal(t *testing.T) {
	ll := &fakeLLM{ready: true, chat: func(_ []llm.ChatMessage) (string, error) {
		return "", errors.New("boom")
	}}
	o := newOrch(ll, kbTool(nil), &fakeSink{}, &fakeExec{})
	b := &board{query: "分析一下"}

	o.mapNode(context.Background(), "t1", dsl.Node{ID: "m1"}, b, o.tracer("t1"))
	if b.fatalErr == nil {
		t.Fatalf("全部子项失败时应置 fatalErr（否则任务会静默判 done-空）")
	}
}
