package subsession

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/llm"
	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/memory"
	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/model"
	"git.yeij.top/AskaEth/Cyrene/pkg/logger"
)

// MemoryRetriever 记忆检索接口
type MemoryRetriever interface {
	Retrieve(ctx context.Context, userID string, query string) ([]memory.MemoryEntry, error)
}

// MemoryProvider 记忆检索子会话提供者
// 职责：检索与当前对话相关的用户记忆，排序去重，返回结构化摘要。
// 支持 LLM 驱动的模糊关键词扩展搜索。
type MemoryProvider struct {
	retriever  MemoryRetriever
	llmAdapter *llm.Adapter
	memClient  *memory.Client
}

// NewMemoryProvider 创建记忆检索子会话提供者
func NewMemoryProvider(retriever MemoryRetriever) *MemoryProvider {
	return &MemoryProvider{
		retriever: retriever,
	}
}

// SetFuzzySearch enables LLM-driven fuzzy keyword expansion for broader memory retrieval.
func (p *MemoryProvider) SetFuzzySearch(llmAdapter *llm.Adapter, memClient *memory.Client) {
	p.llmAdapter = llmAdapter
	p.memClient = memClient
}

func (p *MemoryProvider) Type() model.SubSessionType {
	return model.SubSessionMemory
}

func (p *MemoryProvider) CanHandle(_ context.Context, intent *model.IntentResult, _ string) bool {
	// 如果意图分析明确不需要记忆，则跳过
	if intent != nil && !intent.NeedsMemory {
		// 但为了对话质量，大多数情况下仍然需要记忆
		// 只有明确 negative 时才跳过
		if intent.Sentiment == "neutral" && intent.Primary == "chat" {
			return true
		}
	}
	// 默认总是检索记忆
	return true
}

func (p *MemoryProvider) Priority() int {
	return 2 // 仅次于 General
}

func (p *MemoryProvider) Timeout() time.Duration {
	return 10 * time.Second
}

func (p *MemoryProvider) CreateContext(ctx context.Context, params CreateContextParams) ([]model.LLMMessage, error) {
	// Memory 子会话不依赖 LLM 上下文构建，直接在 Execute 中检索
	// 返回简单上下文供日志记录
	return []model.LLMMessage{
		{Role: model.RoleSystem, Content: "记忆检索子会话"},
		{Role: model.RoleUser, Content: params.UserMessage},
	}, nil
}

func (p *MemoryProvider) Execute(ctx context.Context, subCtx []model.LLMMessage) (*model.SubSessionResult, error) {
	// 从 subCtx 中提取用户消息 (最后一条 user 消息)
	userMessage := ""
	for i := len(subCtx) - 1; i >= 0; i-- {
		if subCtx[i].Role == model.RoleUser {
			userMessage = subCtx[i].Content
			break
		}
	}
	if userMessage == "" {
		return nil, fmt.Errorf("无法从子会话上下文中提取用户消息")
	}

	// 从 context 中提取 userID (通过 context value 传递)
	userID, _ := ctx.Value("userID").(string)
	if userID == "" {
		userID = "unknown"
	}

	result := &model.SubSessionResult{
		Type:       model.SubSessionMemory,
		Memories:   []model.MemorySnippet{},
		Confidence: 0,
	}

	if p.retriever == nil {
		logger.Printf("[memory-subsession] 记忆检索器未初始化")
		result.Summary = "（记忆系统未就绪）"
		return result, nil
	}

	// Phase 1: exact/keyword retrieval
	memories, err := p.retriever.Retrieve(ctx, userID, userMessage)
	if err != nil {
		logger.Printf("[memory-subsession] 记忆检索失败: %v", err)
		result.Error = fmt.Sprintf("检索失败: %v", err)
		result.Summary = "（记忆检索失败，但不影响对话）"
		return result, nil
	}

	seen := make(map[string]bool)
	for _, m := range memories {
		seen[m.ID] = true
	}

	// Phase 2: LLM-driven fuzzy keyword expansion + semantic search
	fuzzyMemories := p.fuzzySearch(ctx, userID, userMessage)
	for _, m := range fuzzyMemories {
		if !seen[m.ID] {
			seen[m.ID] = true
			memories = append(memories, m)
		}
	}

	// 转换为 MemorySnippet
	snippets := make([]model.MemorySnippet, 0, len(memories))
	for _, m := range memories {
		snippets = append(snippets, model.MemorySnippet{
			ID:         m.ID,
			Content:    m.Content,
			Category:   string(m.Category),
			Importance: m.Importance,
			Relevance:  0.5, // 默认相关度
		})
	}

	// 生成摘要
	if len(snippets) == 0 {
		result.Summary = "（没有找到相关记忆）"
	} else {
		result.Summary = fmt.Sprintf("检索到 %d 条相关记忆（含模糊匹配）", len(snippets))
		// 按重要性列出前几条
		topCount := len(snippets)
		if topCount > 3 {
			topCount = 3
		}
		details := ""
		for i := 0; i < topCount; i++ {
			s := snippets[i]
			content := s.Content
			runes := []rune(content)
			if len(runes) > 40 {
				content = string(runes[:40]) + "..."
			}
			details += fmt.Sprintf("- [%s] %s\n", s.Category, content)
		}
		result.Details = details
		result.Confidence = 0.7
	}

	result.Memories = snippets
	logger.Printf("[memory-subsession] 完成: %s (精确=%d, 模糊=%d)", result.Summary, len(memories)-len(fuzzyMemories), len(fuzzyMemories))
	return result, nil
}

// fuzzySearch expands the user message into fuzzy keywords via LLM and performs semantic search.
func (p *MemoryProvider) fuzzySearch(ctx context.Context, userID, userMessage string) []memory.MemoryEntry {
	if p.llmAdapter == nil || p.memClient == nil {
		return nil
	}

	keywords := p.expandKeywords(ctx, userMessage)
	if len(keywords) == 0 {
		return nil
	}

	logger.Printf("[memory-subsession] 模糊关键词: %v", keywords)

	var allResults []memory.MemoryEntry
	seen := make(map[string]bool)

	for _, kw := range keywords {
		results, err := p.memClient.QueryByText(ctx, userID, kw, "", 0, 5)
		if err != nil {
			logger.Printf("[memory-subsession] 模糊搜索 '%s' 失败: %v", kw, err)
			continue
		}
		for _, m := range results {
			if !seen[m.ID] {
				seen[m.ID] = true
				allResults = append(allResults, m)
			}
		}
	}

	return allResults
}

// expandKeywords uses LLM to generate fuzzy/related search keywords from the user message.
func (p *MemoryProvider) expandKeywords(ctx context.Context, message string) []string {
	prompt := fmt.Sprintf(
		"从以下对话消息中提取 3-5 个可用于模糊搜索记忆的关键词。这些关键词应该是：\n"+
			"- 与话题相关的抽象概念\n- 同义词和相关词\n- 更宽泛或更具体的相关概念\n"+
			"- 不要包含消息中已经出现的原词\n\n"+
			"用户消息：「%s」\n\n"+
			"只输出 JSON 字符串数组，例如：[\"关键词1\",\"关键词2\"]", message)

	resp, err := p.llmAdapter.Chat(ctx, []model.LLMMessage{
		{Role: model.RoleSystem, Content: "你是记忆搜索专家。输出 JSON 字符串数组。"},
		{Role: model.RoleUser, Content: prompt},
	})
	if err != nil {
		logger.Printf("[memory-subsession] 关键词扩展失败: %v", err)
		return nil
	}

	text := strings.TrimSpace(resp.Content)
	// Extract JSON array
	if idx := strings.Index(text, "["); idx >= 0 {
		if end := strings.LastIndex(text, "]"); end > idx {
			text = text[idx : end+1]
		}
	}

	var keywords []string
	if err := json.Unmarshal([]byte(text), &keywords); err != nil {
		logger.Printf("[memory-subsession] 解析关键词 JSON 失败: %v (raw=%s)", err, resp.Content)
		return nil
	}

	return keywords
}
