package memory

import (
	"context"
	"encoding/json"
	"fmt"
	"git.yeij.top/AskaEth/Cyrene/pkg/logger"
	"strings"

	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/model"
)

// Extractor 记忆提取器 —— 从对话中提取结构化记忆
type Extractor struct {
	store   *Store
	llmChat func(ctx context.Context, messages []model.LLMMessage) (*model.LLMResponse, error)
}

// NewExtractor 创建记忆提取器
// llmChat: LLM对话函数，用于分析对话内容并提取记忆
// 如果为nil，则使用规则提取（降级模式）
func NewExtractor(store *Store, llmChat func(ctx context.Context, messages []model.LLMMessage) (*model.LLMResponse, error)) *Extractor {
	return &Extractor{
		store:   store,
		llmChat: llmChat,
	}
}

// ExtractAndStore 从一轮对话中提取记忆并存储
// 异步执行，不阻塞主流程
func (e *Extractor) ExtractAndStore(ctx context.Context, userID, sessionID, userMessage, assistantResponse string) {
	memories, err := e.extract(ctx, userMessage, assistantResponse)
	if err != nil {
		logger.Printf("[memory] 记忆提取失败: %v", err)
		return
	}
	e.storeMemories(ctx, userID, sessionID, memories)
}

// ExtractObservations 从观察到的单条消息中提取记忆（无语境回复）。
// 用于 platform_silent 模式：昔涟被动观察群聊，提取值得记住的信息。
func (e *Extractor) ExtractObservations(ctx context.Context, userID, sessionID, message string) {
	memories, err := e.extractObservations(ctx, message)
	if err != nil {
		logger.Printf("[memory] 观察记忆提取失败: %v", err)
		return
	}
	e.storeMemories(ctx, userID, sessionID, memories)
}

func (e *Extractor) storeMemories(ctx context.Context, userID, sessionID string, memories []model.MemoryEntry) {
	for _, mem := range memories {
		mem.UserID = userID
		mem.SessionID = sessionID
		mem.Source = "conversation"

		existing, err := e.findSimilar(ctx, userID, &mem)
		if err == nil && existing != nil {
			e.mergeMemory(ctx, existing, &mem)
			continue
		}

		if err := e.store.Save(ctx, &mem); err != nil {
			logger.Printf("[memory] 记忆保存失败: %v", err)
			continue
		}
		logger.Printf("[memory] 新记忆已保存 [%s|%d★]: %s", mem.Category, mem.Importance, mem.Summary)
	}
}

// extractObservations 从观察到的消息中提取记忆（无助手回复）
func (e *Extractor) extractObservations(ctx context.Context, message string) ([]model.MemoryEntry, error) {
	if e.llmChat != nil {
		return e.extractObservationsWithLLM(ctx, message)
	}
	return e.extractWithRules(message, ""), nil
}

// extractObservationsWithLLM 使用LLM从观察到的消息中提取值得记住的信息
func (e *Extractor) extractObservationsWithLLM(ctx context.Context, message string) ([]model.MemoryEntry, error) {
	prompt := fmt.Sprintf(`分析以下在聊天平台观察到的消息，提取值得记住的信息作为记忆。

观察到的消息: %s

请以JSON格式返回提取的记忆。这条消息来自群聊/频道，昔涟只是旁观者。
消息格式为：[群聊 群号] 发送者昵称 (OBv11账号)：消息内容
提取角度：这条消息中包含了什么关于消息发送者、讨论主题、事件或氛围的信息？
重要：请以实际发送者的名字为主语（如"某某说..."），不要统一用"开拓者"称呼所有发言者。

每条记忆需要包含以下字段：
- content: 完整的记忆内容（一句话描述，客观准确）
- summary: 简短摘要（10字以内）
- category: 记忆分类，必须是以下之一：
  * conversation: 对话主题/讨论摘要
  * event: 事件记录（发生了什么）
  * personal_info: 参与者的个人信息
  * knowledge: 知识性信息
  * user_preference: 某人的偏好
  * task: 提及的计划/任务
- priority: 优先级 (0=临时, 1=普通, 2=重要, 3=核心)
- importance: 重要程度 1-10
  * 1-3: 日常闲聊，不太重要
  * 4-6: 一般有用的信息
  * 7-8: 重要信息，值得长期记住
  * 9-10: 核心信息
- keywords: 关键词标签数组（3-5个词）

只提取有意义的信息。如果消息只是日常寒暄或无实质内容，返回空数组。

输出格式:
{"memories": [{"content": "...", "summary": "...", "category": "...", "priority": 1, "importance": 6, "keywords": ["词1", "词2"]}]}
`, message)

	resp, err := e.llmChat(ctx, []model.LLMMessage{
		{Role: "system", Content: "你是一个聊天观察记录助手。你只输出JSON格式的结果。你的任务是从观察到的聊天消息中提取值得记住的信息。"},
		{Role: "user", Content: prompt},
	})
	if err != nil {
		return nil, fmt.Errorf("LLM提取观察记忆失败: %w", err)
	}

	return e.parseExtractionResult(resp.Content)
}

// extract 从对话中提取记忆
func (e *Extractor) extract(ctx context.Context, userMessage, assistantResponse string) ([]model.MemoryEntry, error) {
	// 如果有LLM，使用LLM提取
	if e.llmChat != nil {
		return e.extractWithLLM(ctx, userMessage, assistantResponse)
	}
	// 降级：规则提取
	return e.extractWithRules(userMessage, assistantResponse), nil
}

// MemoryExtractionResult LLM提取结果的结构
type MemoryExtractionResult struct {
	Memories []ExtractedMemory `json:"memories"`
}

// ExtractedMemory LLM提取的原始记忆条目
type ExtractedMemory struct {
	Content    string   `json:"content"`
	Summary    string   `json:"summary"`
	Category   string   `json:"category"`
	Priority   int      `json:"priority"`
	Importance int      `json:"importance"` // 重要程度 1-10
	Keywords   []string `json:"keywords"`   // 关键词标签
}

// extractWithLLM 使用LLM提取记忆
func (e *Extractor) extractWithLLM(ctx context.Context, userMessage, assistantResponse string) ([]model.MemoryEntry, error) {
	prompt := fmt.Sprintf(`分析以下对话，提取关于用户（开拓者）的重要信息作为记忆。

用户消息: %s
昔涟回复: %s

请以JSON格式返回提取的记忆。每条记忆需要包含以下字段：
- content: 完整的记忆内容（一句话描述，客观准确）
- summary: 简短摘要（10字以内）
- category: 记忆分类，必须是以下之一：
  * user_preference: 用户偏好（食物、颜色、习惯、爱好）
  * personal_info: 个人信息（姓名、年龄、职业、住址）
  * conversation: 对话摘要（值得记住的对话主题）
  * knowledge: 知识性信息（用户分享的知识或观点）
  * event: 事件记录（发生了什么事）
  * task: 任务/计划（用户的计划、待办事项）
  * relationship: 关系信息（用户与他人的关系）
- priority: 优先级 (0=临时, 1=普通, 2=重要, 3=核心)
- importance: 重要程度 1-10（评估这条信息对了解用户有多重要）
  * 1-3: 琐碎信息，可能很快过时
  * 4-6: 一般有用，值得记住
  * 7-8: 重要信息，长期有用
  * 9-10: 核心信息，对理解用户至关重要
- keywords: 关键词标签数组（3-5个词，用于检索和匹配）

重要性评估指南：
- 用户明确表达的偏好（喜欢/讨厌）→ importance 7-8
- 用户的基本个人信息（姓名/生日）→ importance 9-10
- 日常闲聊主题 → importance 2-3
- 用户提到的计划/任务 → importance 5-7
- 用户的情感状态 → importance 5-6

只提取有意义的信息，不要提取无意义的闲聊。如果没有值得记住的内容，返回空数组。

输出格式:
{"memories": [{"content": "...", "summary": "...", "category": "...", "priority": 1, "importance": 6, "keywords": ["词1", "词2"]}]}
`, userMessage, assistantResponse)

	resp, err := e.llmChat(ctx, []model.LLMMessage{
		{Role: "system", Content: "你是一个记忆提取助手。你只输出JSON格式的结果，不输出其他内容。你的任务是评估对话中关于用户的信息，提取值得记住的内容，并为其打分。"},
		{Role: "user", Content: prompt},
	})
	if err != nil {
		return nil, fmt.Errorf("LLM提取记忆失败: %w", err)
	}

	entries, err := e.parseExtractionResult(resp.Content)
	if err != nil {
		return nil, err
	}
	return entries, nil
}

// parseExtractionResult 解析LLM返回的记忆提取JSON结果
func (e *Extractor) parseExtractionResult(text string) ([]model.MemoryEntry, error) {
	result := MemoryExtractionResult{}
	content := extractJSON(text)
	if err := json.Unmarshal([]byte(content), &result); err != nil {
		var arrResult []ExtractedMemory
		if err2 := json.Unmarshal([]byte(content), &arrResult); err2 != nil {
			return nil, fmt.Errorf("解析记忆JSON失败: %w (原始: %s)", err, content[:minint(len(content), 100)])
		}
		result.Memories = arrResult
	}

	var entries []model.MemoryEntry
	for _, m := range result.Memories {
		cat := model.MemoryCategory(m.Category)
		if cat == "" {
			cat = model.CategoryKnowledge
		}

		pri := model.MemoryPriority(m.Priority)
		if pri < 0 || pri > 3 {
			pri = model.MemoryNormal
		}

		imp := m.Importance
		if imp < 1 {
			imp = 5
		}
		if imp > 10 {
			imp = 10
		}

		entries = append(entries, model.MemoryEntry{
			Content:    m.Content,
			Summary:    m.Summary,
			Category:   cat,
			Priority:   pri,
			Importance: imp,
			Keywords:   m.Keywords,
		})
	}

	return entries, nil
}

// extractWithRules 基于规则提取记忆（降级方案）
func (e *Extractor) extractWithRules(userMessage, _ string) []model.MemoryEntry {
	var entries []model.MemoryEntry

	// 规则: 检测用户偏好表达 - 使用新的分类体系
	prefPatterns := map[string]struct {
		category   model.MemoryCategory
		importance int
	}{
		"喜欢":   {model.CategoryUserPreference, 7},
		"爱":     {model.CategoryUserPreference, 8},
		"最喜欢": {model.CategoryUserPreference, 9},
		"讨厌":   {model.CategoryUserPreference, 8},
		"不喜欢": {model.CategoryUserPreference, 7},
		"经常":   {model.CategoryUserPreference, 6},
		"每天都": {model.CategoryUserPreference, 6},
		"一直":   {model.CategoryUserPreference, 5},
		"我叫":   {model.CategoryPersonalInfo, 9},
		"我是":   {model.CategoryPersonalInfo, 8},
		"我家":   {model.CategoryPersonalInfo, 7},
		"住在":   {model.CategoryPersonalInfo, 8},
		"生日":   {model.CategoryPersonalInfo, 10},
		"计划":   {model.CategoryTask, 6},
		"打算":   {model.CategoryTask, 6},
		"去了":   {model.CategoryEvent, 4},
		"发生":   {model.CategoryEvent, 4},
	}

	for pattern, info := range prefPatterns {
		if idx := strings.Index(userMessage, pattern); idx != -1 {
			// 提取包含关键词的句子片段
			start := maxint(0, idx-5)
			runes := []rune(userMessage)
			end := minint(len(runes), idx+len([]rune(pattern))+15)
			content := strings.TrimSpace(string(runes[start:end]))

			entries = append(entries, model.MemoryEntry{
				Content:    content,
				Summary:    truncateString(content, 20),
				Category:   info.category,
				Priority:   model.MemoryNormal,
				Importance: info.importance,
				Keywords:   []string{pattern},
			})
			break // 每条消息最多提取一条规则记忆
		}
	}

	return entries
}

// findSimilar 查找与给定记忆相似的已有记忆
func (e *Extractor) findSimilar(ctx context.Context, userID string, newMem *model.MemoryEntry) (*model.MemoryEntry, error) {
	existing, err := e.store.Query(ctx, model.MemoryQuery{
		UserID: userID,
		Limit:  100,
	})
	if err != nil {
		return nil, err
	}

	for i := range existing {
		score := existing[i].SimilarityScore(newMem)
		if score >= deDupThreshold {
			return &existing[i], nil
		}
	}

	return nil, nil
}

// mergeMemory 合并新记忆到已有记忆
func (e *Extractor) mergeMemory(ctx context.Context, existing *model.MemoryEntry, newMem *model.MemoryEntry) {
	// 更新内容（如果新内容更有价值）
	if newMem.Importance > existing.Importance || len(newMem.Content) > len(existing.Content) {
		existing.Content = newMem.Content
		existing.Summary = newMem.Summary
	}

	// 合并关键词
	keywordSet := make(map[string]bool)
	for _, k := range existing.Keywords {
		keywordSet[k] = true
	}
	for _, k := range newMem.Keywords {
		keywordSet[k] = true
	}
	mergedKeywords := make([]string, 0, len(keywordSet))
	for k := range keywordSet {
		mergedKeywords = append(mergedKeywords, k)
	}
	existing.Keywords = mergedKeywords

	// 取最高重要性
	if newMem.Importance > existing.Importance {
		existing.Importance = newMem.Importance
	}

	// 取最高优先级
	if newMem.Priority > existing.Priority {
		existing.Priority = newMem.Priority
	}

	// 增加访问计数（因为又被"想起"了）
	existing.AccessCount++

	if err := e.store.Update(ctx, existing); err != nil {
		logger.Printf("[memory] 合并记忆更新失败: %v", err)
		return
	}

	logger.Printf("[memory] 合并记忆 [%s|%d★]: %s (相似度 > %.0f%%)",
		existing.Category, existing.Importance, existing.Summary, deDupThreshold*100)
}

// extractJSON 从LLM回复中提取JSON内容
func extractJSON(text string) string {
	text = strings.TrimSpace(text)

	// 移除 markdown 代码块标记
	if strings.HasPrefix(text, "```json") {
		text = strings.TrimPrefix(text, "```json")
		text = strings.TrimSuffix(text, "```")
		text = strings.TrimSpace(text)
	} else if strings.HasPrefix(text, "```") {
		text = strings.TrimPrefix(text, "```")
		text = strings.TrimSuffix(text, "```")
		text = strings.TrimSpace(text)
	}

	return text
}

func truncateString(s string, maxLen int) string {
	runes := []rune(s)
	if len(runes) <= maxLen {
		return s
	}
	return string(runes[:maxLen]) + "..."
}

func minint(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func maxint(a, b int) int {
	if a > b {
		return a
	}
	return b
}
