package llm

import (
	"context"
	"testing"
	"time"

	"github.com/cloudwego/eino/components/model"
	"github.com/cloudwego/eino/schema"
)

func msgs(text string) []*schema.Message { return []*schema.Message{schema.UserMessage(text)} }

// TestCache_HitSkipsInner 同输入第二次命中缓存，不再调底层模型。
func TestCache_HitSkipsInner(t *testing.T) {
	var calls int
	cm := &cachingModel{
		inner:    &fakeModel{reply: "答案", calls: &calls},
		cache:    newRespCache(time.Minute, 100),
		keyExtra: "m:test",
	}
	ctx := context.Background()
	a1, _ := cm.Generate(ctx, msgs("问题"))
	a2, _ := cm.Generate(ctx, msgs("问题"))
	if a1.Content != "答案" || a2.Content != "答案" {
		t.Fatalf("两次都应得答案, got %q %q", a1.Content, a2.Content)
	}
	if calls != 1 {
		t.Fatalf("第二次应命中缓存、底层只调一次，got calls=%d", calls)
	}
}

// TestCache_DifferentInputMiss 不同输入不串味，各调一次。
func TestCache_DifferentInputMiss(t *testing.T) {
	var calls int
	cm := &cachingModel{inner: &fakeModel{reply: "x", calls: &calls}, cache: newRespCache(time.Minute, 100), keyExtra: "m:test"}
	ctx := context.Background()
	cm.Generate(ctx, msgs("A"))
	cm.Generate(ctx, msgs("B"))
	if calls != 2 {
		t.Fatalf("不同输入应各调一次，got calls=%d", calls)
	}
}

// TestCache_TTLExpiry 过期后重新调底层。
func TestCache_TTLExpiry(t *testing.T) {
	var calls int
	cm := &cachingModel{inner: &fakeModel{reply: "x", calls: &calls}, cache: newRespCache(20*time.Millisecond, 100), keyExtra: "m:test"}
	ctx := context.Background()
	cm.Generate(ctx, msgs("Q"))
	time.Sleep(40 * time.Millisecond)
	cm.Generate(ctx, msgs("Q")) // 已过期 → 再调
	if calls != 2 {
		t.Fatalf("过期后应重新调底层，got calls=%d", calls)
	}
}

// TestCache_StreamNotCached 流式不缓存，每次都调底层。
func TestCache_StreamNotCached(t *testing.T) {
	var calls int
	cm := &cachingModel{inner: &fakeModel{reply: "x", calls: &calls}, cache: newRespCache(time.Minute, 100), keyExtra: "m:test"}
	ctx := context.Background()
	for i := 0; i < 2; i++ {
		sr, _ := cm.Stream(ctx, msgs("Q"))
		sr.Close()
	}
	if calls != 2 {
		t.Fatalf("流式不应缓存，got calls=%d", calls)
	}
}

// TestCache_WithToolsDifferentKey 不同工具集 → 不同缓存键，不串味。
func TestCache_WithToolsDifferentKey(t *testing.T) {
	var calls int
	base := &cachingModel{inner: &fakeModel{reply: "x", calls: &calls}, cache: newRespCache(time.Minute, 100), keyExtra: "m:test"}
	ctx := context.Background()
	toolA, _ := base.WithTools([]*schema.ToolInfo{{Name: "a", Desc: "A"}})
	toolB, _ := base.WithTools([]*schema.ToolInfo{{Name: "b", Desc: "B"}})
	toolA.Generate(ctx, msgs("Q"))
	toolB.Generate(ctx, msgs("Q")) // 同输入但工具集不同 → 不命中
	if calls != 2 {
		t.Fatalf("不同工具集不应串味，got calls=%d", calls)
	}
}

// TestWithCache_Disabled TTL=0 时不包缓存，直接返回原模型。
func TestWithCache_Disabled(t *testing.T) {
	t.Setenv("LLM_CACHE_TTL_S", "0")
	inner := &fakeModel{reply: "x"}
	got := withCache(inner, "test")
	if _, isCaching := got.(*cachingModel); isCaching {
		t.Fatal("TTL=0 应禁用缓存，不应包 cachingModel")
	}
	var _ model.ToolCallingChatModel = got
}
