package llm

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"os"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

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

// cacheTTLSeconds 是输出缓存的过期时长（env LLM_CACHE_TTL_S，单位秒；0=关闭缓存）。默认 60s：
// 只覆盖"短时间内的重试/重复点击/双发"——这类几乎一定是同一意图，命中省成本+提速；
// 又短到不会让助手对同一问题长期"复读"同一答案（创作型输出仍随时间变化）。
func cacheTTLSeconds() time.Duration {
	// envInt 允许 0（关闭）；默认 60 秒。
	if v := os.Getenv("LLM_CACHE_TTL_S"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n >= 0 {
			return time.Duration(n) * time.Second
		}
	}
	return 60 * time.Second
}

// cacheMax 是缓存条目上限（env LLM_CACHE_MAX，默认 512）。满了随机淘汰一条。
func cacheMax() int {
	if v := os.Getenv("LLM_CACHE_MAX"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 {
			return n
		}
	}
	return 512
}

// respCache 是带 TTL + 容量上限的 Generate 结果缓存（并发安全）。
type respCache struct {
	mu     sync.Mutex
	m      map[string]cacheEntry
	ttl    time.Duration
	max    int
	hits   int64
	misses int64
}

type cacheEntry struct {
	msg *schema.Message
	exp time.Time
}

func newRespCache(ttl time.Duration, max int) *respCache {
	return &respCache{m: make(map[string]cacheEntry), ttl: ttl, max: max}
}

func (c *respCache) get(key string) (*schema.Message, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	e, ok := c.m[key]
	if !ok || time.Now().After(e.exp) {
		if ok {
			delete(c.m, key) // 过期清理
		}
		atomic.AddInt64(&c.misses, 1)
		return nil, false
	}
	atomic.AddInt64(&c.hits, 1)
	return e.msg, true
}

func (c *respCache) put(key string, msg *schema.Message) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if len(c.m) >= c.max {
		for k := range c.m { // 满 → 随机淘汰一条（map 迭代序随机）
			delete(c.m, k)
			break
		}
	}
	c.m[key] = cacheEntry{msg: msg, exp: time.Now().Add(c.ttl)}
}

// cachingModel 缓存 Generate 结果：同输入命中则跳过 LLM 调用（省成本+提速）。
// 只缓存 Generate（非流式）；Stream 是用户可见的创作型输出，重复率低且回放复杂，直接透传不缓存。
// 键 = keyExtra（模型名 + 绑定工具哈希）+ 消息内容哈希——确保不同模型/工具集/输入互不串味。
type cachingModel struct {
	inner    model.ToolCallingChatModel
	cache    *respCache
	keyExtra string
}

// withCache 把模型包成带缓存的（ttl<=0 则不包，直接返回原模型）。modelID 用于隔离不同模型的缓存。
func withCache(inner model.ToolCallingChatModel, modelID string) model.ToolCallingChatModel {
	ttl := cacheTTLSeconds()
	if ttl <= 0 {
		return inner
	}
	return &cachingModel{inner: inner, cache: newRespCache(ttl, cacheMax()), keyExtra: "m:" + modelID}
}

func (c *cachingModel) Generate(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.Message, error) {
	key := c.keyExtra + "|" + hashMessages(input)
	if msg, ok := c.cache.get(key); ok {
		fmt.Printf("[llm] 输出缓存命中（跳过 LLM 调用，命中/未命中=%d/%d）\n",
			atomic.LoadInt64(&c.cache.hits), atomic.LoadInt64(&c.cache.misses))
		return msg, nil
	}
	out, err := c.inner.Generate(ctx, input, opts...)
	if err == nil && out != nil {
		c.cache.put(key, out)
	}
	return out, err
}

// Stream 不缓存（用户可见的创作型输出），直接透传。
func (c *cachingModel) Stream(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.StreamReader[*schema.Message], error) {
	return c.inner.Stream(ctx, input, opts...)
}

// WithTools 给内层绑工具，返回共享同一缓存但键加了工具哈希的新包装（不同工具集不串味）。
func (c *cachingModel) WithTools(tools []*schema.ToolInfo) (model.ToolCallingChatModel, error) {
	inner, err := c.inner.WithTools(tools)
	if err != nil {
		return nil, err
	}
	return &cachingModel{inner: inner, cache: c.cache, keyExtra: c.keyExtra + "|tools:" + hashTools(tools)}, nil
}

// hashMessages 把消息序列（角色+内容）哈希为稳定 key。
func hashMessages(msgs []*schema.Message) string {
	h := sha256.New()
	for _, m := range msgs {
		fmt.Fprintf(h, "%s\x00%s\x01", m.Role, m.Content)
	}
	return hex.EncodeToString(h.Sum(nil))
}

// hashTools 把绑定工具的名字+描述哈希（工具集不同 → 缓存键不同）。
func hashTools(tools []*schema.ToolInfo) string {
	if len(tools) == 0 {
		return "none"
	}
	h := sha256.New()
	for _, t := range tools {
		fmt.Fprintf(h, "%s\x00%s\x01", t.Name, t.Desc)
	}
	return hex.EncodeToString(h.Sum(nil))[:16]
}
