package eino

import (
	"context"
	"encoding/json"
	"sync"
	"testing"
	"time"

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

// ---- Handle 集成测试：覆盖真正的任务入口（熔断→输入护栏→状态机→执行→评测/用量），
//      而非直接打 runGraph。把核心编排链 + harness 治理栈的协同钉死。----

// fakeStatus 记录任务状态流转序列。
type fakeStatus struct {
	mu     sync.Mutex
	seq    []string
	detail string
}

func (s *fakeStatus) PublishTaskStatus(_, status, detail string) error {
	s.mu.Lock()
	s.seq = append(s.seq, status)
	s.detail = detail
	s.mu.Unlock()
	return nil
}
func (s *fakeStatus) snapshot() ([]string, string) {
	s.mu.Lock()
	defer s.mu.Unlock()
	return append([]string{}, s.seq...), s.detail
}
func (s *fakeStatus) last() string {
	seq, _ := s.snapshot()
	if len(seq) == 0 {
		return ""
	}
	return seq[len(seq)-1]
}

// fakeUsageSink 捕获 token 用量回写（并发安全：Handle 在 defer/goroutine 收尾时发）。
type fakeUsageSink struct {
	mu sync.Mutex
	ev *contract.UsageEvent
}

func (u *fakeUsageSink) PublishUsage(ev *contract.UsageEvent) error {
	u.mu.Lock()
	u.ev = ev
	u.mu.Unlock()
	return nil
}
func (u *fakeUsageSink) get() *contract.UsageEvent {
	u.mu.Lock()
	defer u.mu.Unlock()
	return u.ev
}

// handleKit 是 Handle 测试的依赖套件（全 fake，可断言各出口）。
type handleKit struct {
	o      *Orchestrator
	sink   *fakeSink
	status *fakeStatus
	exec   *fakeExec
	eval   *fakeEvalSink
	usage  *fakeUsageSink
	tools  *fakeTools
}

func newHandleKit(ll *fakeLLM) *handleKit {
	k := &handleKit{
		sink: &fakeSink{}, status: &fakeStatus{}, exec: &fakeExec{},
		eval: &fakeEvalSink{}, usage: &fakeUsageSink{}, tools: &fakeTools{},
	}
	o := &Orchestrator{
		pool: ll, breaker: harness.NewCircuitBreaker(),
		sink: k.sink, tools: k.tools, exec: k.exec, status: k.status, evalSink: k.eval,
	}
	o.SetUsageSink(k.usage)
	k.o = o
	return k
}

// eventually 在 timeout 内轮询 cond，成立即返回 true（等异步评测/用量回写）。
func eventually(timeout time.Duration, cond func() bool) bool {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		if cond() {
			return true
		}
		time.Sleep(10 * time.Millisecond)
	}
	return cond()
}

const convGraph = `{"nodes":[
	{"id":"i","kind":"input","config":{"text":"介绍杭州"}},
	{"id":"a","kind":"agent","config":{"system":"助手"}}
],"edges":[{"source":"i","target":"a"}]}`

// happy path：状态 running→done，token 流出 + 收尾，异步评测落库。
func TestHandle_HappyPath(t *testing.T) {
	ll := &fakeLLM{ready: true, stream: func([]llm.ChatMessage) string { return "杭州是浙江省会，以西湖闻名。" }}
	k := newHandleKit(ll)
	k.o.eval = harness.NewEvaluator(func() bool { return true },
		func(context.Context, string, string) (string, error) { return `{"score":5,"reason":"好"}`, nil })

	if err := k.o.Handle(context.Background(), task(convGraph)); err != nil {
		t.Fatalf("Handle err: %v", err)
	}
	seq, _ := k.status.snapshot()
	if len(seq) < 2 || seq[0] != contract.TaskRunning || seq[len(seq)-1] != contract.TaskDone {
		t.Fatalf("状态应 running→…→done，got %v", seq)
	}
	if !k.sink.done {
		t.Error("应收尾 token 流")
	}
	if k.sink.text() == "" {
		t.Error("应有 token 流出")
	}
	if !eventually(2*time.Second, func() bool { return k.eval.get() != nil }) {
		t.Fatal("异步评测应落库")
	}
	if ev := k.eval.get(); ev.Level != contract.EvalOK {
		t.Errorf("评测应 ok，got %s", ev.Level)
	}
}

// 工具→agent 链路经 Handle 全程跑通：工具被调，产出注入答案。
func TestHandle_ToolFeedsAgent(t *testing.T) {
	g := `{"nodes":[
		{"id":"i","kind":"input","config":{"text":"hi"}},
		{"id":"t","kind":"tool","config":{"tool":"wiki_search"}},
		{"id":"a","kind":"agent","config":{"system":"S"}}
	],"edges":[{"source":"i","target":"t"},{"source":"t","target":"a"}]}`
	ll := &fakeLLM{ready: true, stream: func(m []llm.ChatMessage) string { return m[0].Content }}
	k := newHandleKit(ll)
	k.tools.fn = func(*contract.ToolCall) *contract.ToolResult {
		return &contract.ToolResult{OK: true, Content: "TOOLDATA"}
	}
	if err := k.o.Handle(context.Background(), task(g)); err != nil {
		t.Fatalf("Handle err: %v", err)
	}
	if !k.tools.called("wiki_search") {
		t.Error("应调用 wiki_search")
	}
	if k.status.last() != contract.TaskDone {
		t.Errorf("应 done，got %s", k.status.last())
	}
}

// 熔断开启：快速拒绝，不执行，状态 failed。
func TestHandle_CircuitBreakerOpen(t *testing.T) {
	ll := &fakeLLM{ready: true, stream: func([]llm.ChatMessage) string { return "不该被调用" }}
	k := newHandleKit(ll)
	for i := 0; i < 10; i++ { // 连续失败打开熔断
		k.o.breaker.Report(false)
	}
	if k.o.breaker.Allow() {
		t.Skip("熔断阈值未达（实现差异），跳过")
	}
	if err := k.o.Handle(context.Background(), task(convGraph)); err != nil {
		t.Fatalf("Handle err: %v", err)
	}
	if k.status.last() != contract.TaskFailed {
		t.Errorf("熔断应 failed，got %s", k.status.last())
	}
	if k.sink.text() != "" && k.sink.text() != "⚠️ 服务繁忙（已触发熔断保护），请稍后重试。" {
		t.Errorf("熔断不应执行图，got token %q", k.sink.text())
	}
}

// token 预算触顶：状态 failed（带原因），用量回写带 Exceeded。
func TestHandle_BudgetExceeded(t *testing.T) {
	ll := &fakeLLM{ready: true, stream: func([]llm.ChatMessage) string { return "答案" }}
	k := newHandleKit(ll)
	tk := task(convGraph)
	tk.Meta[contract.MetaTokenBudget] = float64(3) // 极小预算，入口计输入即触顶

	if err := k.o.Handle(context.Background(), tk); err != nil {
		t.Fatalf("Handle err: %v", err)
	}
	if k.status.last() != contract.TaskFailed {
		t.Fatalf("预算超限应 failed，got %s", k.status.last())
	}
	if !eventually(2*time.Second, func() bool { u := k.usage.get(); return u != nil && u.Exceeded }) {
		t.Fatal("应回写用量且标记 Exceeded")
	}
}

// 输入护栏 Tier2：灰区任务被 LLM 分类器拦截 → rejected，不执行。
func TestHandle_GuardrailBlocks(t *testing.T) {
	ll := &fakeLLM{ready: true, stream: func([]llm.ChatMessage) string { return "不该被调用" }}
	k := newHandleKit(ll)
	k.o.SetGuardian(harness.NewClassifier(func() bool { return true },
		func(context.Context, string, string) (string, error) {
			return `{"jailbreak":true,"severity":0.95,"reason":"越狱尝试"}`, nil
		}))
	tk := task(convGraph)
	tk.Meta[contract.MetaSafetyCheck] = true

	if err := k.o.Handle(context.Background(), tk); err != nil {
		t.Fatalf("Handle err: %v", err)
	}
	if k.status.last() != contract.TaskRejected {
		t.Fatalf("应 rejected，got %s", k.status.last())
	}
	seq, _ := k.status.snapshot()
	for _, s := range seq {
		if s == contract.TaskRunning {
			t.Error("被护栏拦截不应进入 running")
		}
	}
}

// 空 id 任务：直接丢弃，无状态回写（防脏数据自我放大）。
func TestHandle_EmptyTaskDropped(t *testing.T) {
	k := newHandleKit(&fakeLLM{ready: true})
	if err := k.o.Handle(context.Background(), &contract.Task{ID: "", Graph: json.RawMessage(convGraph)}); err != nil {
		t.Fatalf("Handle err: %v", err)
	}
	if seq, _ := k.status.snapshot(); len(seq) != 0 {
		t.Errorf("空任务不应有状态回写，got %v", seq)
	}
}
