package main

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"path/filepath"
	"strings"
	"time"
	"unicode/utf8"

	_ "modernc.org/sqlite"
)

const cpaURL = "https://cpa.1216.top/v1/chat/completions"
const cpaModel = "glm-5.1"
const minKnowledgeRunes = 80

type knowledgeData struct {
	Content string `json:"content"`
	Keyword string `json:"keyword"`
}

var knowledgeDB *sql.DB

func initKnowledgeDB() {
	dbPath := filepath.Join(configDir(), "knowledge.db")
	var err error
	knowledgeDB, err = sql.Open("sqlite", dbPath)
	if err != nil {
		log.Println("知识库打开失败:", err)
		return
	}
	knowledgeDB.SetMaxOpenConns(1)
	_, err = knowledgeDB.Exec(`CREATE TABLE IF NOT EXISTS knowledge_cards (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		keyword TEXT NOT NULL,
		content TEXT NOT NULL,
		created_at DATETIME DEFAULT CURRENT_TIMESTAMP
	)`)
	if err != nil {
		log.Println("知识库建表失败:", err)
	}
}

func saveKnowledgeCard(keyword, content string) {
	if knowledgeDB == nil {
		return
	}
	_, err := knowledgeDB.Exec("INSERT INTO knowledge_cards (keyword, content) VALUES (?, ?)", keyword, content)
	if err != nil {
		log.Println("知识保存失败:", err)
	}
}

func getRandomKnowledgeCard(keyword string) string {
	if knowledgeDB == nil {
		return ""
	}
	rows, err := knowledgeDB.Query(
		"SELECT content FROM knowledge_cards WHERE keyword = ? ORDER BY RANDOM() LIMIT 20",
		keyword,
	)
	if err != nil {
		return ""
	}
	defer rows.Close()
	for rows.Next() {
		var content string
		if rows.Scan(&content) == nil {
			content = normalizeKnowledgeContent(content)
			if isQualityKnowledgeCard(content) {
				return content
			}
		}
	}
	return ""
}

func getKnowledgeCardCount(keyword string) int {
	if knowledgeDB == nil {
		return 0
	}
	var count int
	err := knowledgeDB.QueryRow(
		"SELECT COUNT(*) FROM knowledge_cards WHERE keyword = ?",
		keyword,
	).Scan(&count)
	if err != nil {
		return 0
	}
	return count
}

func fetchKnowledgeFromLLM(keyword string, cfg *Config) string {
	basePrompt := buildKnowledgePrompt(keyword, cfg.KnowledgePrompt)
	messages := []map[string]string{
		{
			"role": "system",
			"content": `你是一位资深技术专家，面向有多年经验的开发者撰写知识卡片。

内容标准：
- 必须包含可直接用于生产的硬核知识：底层原理、性能陷阱、边界条件、源码级细节
- 优先讲"为什么"而非"是什么"——机制、权衡、踩坑经验
- 用具体数值、代码片段（仅关键词级别）、或真实场景佐证
- 读者看完能立刻在项目中应用或避免踩坑

禁止：
- 入门级科普、"是什么"式定义、百科搬运
- 空泛鸡汤（"很重要"、"提升效率"、"广泛应用"）
- 标题、序号、Markdown标记、emoji`,
		},
		{"role": "user", "content": basePrompt},
	}

	body := map[string]interface{}{
		"model":                 cpaModel,
		"max_completion_tokens": 2048,
		"temperature":           0.55,
		"messages":              messages,
	}
	content := requestKnowledgeCompletion(body)
	content = normalizeKnowledgeContent(content)
	if isQualityKnowledgeCard(content) {
		return content
	}

	log.Printf("知识卡片质量不足，重试: %q", content)
	messages = append(messages, map[string]string{
		"role": "user",
		"content": fmt.Sprintf(
			"上一条太短或信息密度不足。请重写一条「%s」知识卡片：120-180个中文字符，必须包含一个明确机制/原理、一个具体例子或应用场景、一个结论。只输出正文。",
			keyword,
		),
	})
	body["messages"] = messages
	content = requestKnowledgeCompletion(body)
	content = normalizeKnowledgeContent(content)
	if isQualityKnowledgeCard(content) {
		return content
	}

	return ""
}

func buildKnowledgePrompt(keyword, customPrompt string) string {
	basePrompt := fmt.Sprintf(`围绕「%s」写一条面向资深开发者的技术知识卡片。

要求：
- 100-160字，2-3句连贯正文
- 必须触及底层机制、性能细节或生产踩坑经验
- 必须包含一个具体场景或数值佐证（如延迟数据、内存阈值、版本差异等）
- 直接输出正文，不要标题、编号、Markdown`, keyword)
	if customPrompt != "" {
		basePrompt += "\n附加风格要求（不能覆盖上面的字数和质量要求）：" + customPrompt
	}
	return basePrompt
}

func requestKnowledgeCompletion(body map[string]interface{}) string {
	jsonData, _ := json.Marshal(body)

	req, err := http.NewRequest("POST", cpaURL, bytes.NewReader(jsonData))
	if err != nil {
		log.Println("知识API请求创建失败:", err)
		return ""
	}
	key := loadConfig().cpaKey()
	if key == "" {
		log.Println("未配置知识卡片 API Key")
		return ""
	}
	req.Header.Set("Authorization", "Bearer "+key)
	req.Header.Set("Content-Type", "application/json")

	resp, err := httpClient.Do(req)
	if err != nil {
		log.Println("知识API请求失败:", err)
		return ""
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		log.Printf("知识API返回状态码: %d", resp.StatusCode)
		return ""
	}

	var result struct {
		Choices []struct {
			Message struct {
				Content          string `json:"content"`
				ReasoningContent string `json:"reasoning_content"`
			} `json:"message"`
		} `json:"choices"`
	}
	if json.NewDecoder(resp.Body).Decode(&result) != nil {
		log.Println("知识API响应解析失败")
		return ""
	}
	if len(result.Choices) > 0 {
		c := result.Choices[0].Message.Content
		if c == "" {
			c = result.Choices[0].Message.ReasoningContent
		}
		return c
	}
	return ""
}

// normalizeKnowledgeContent 清洗 LLM 输出的格式残留（Markdown标记、标题前缀、多余空白）
func normalizeKnowledgeContent(content string) string {
	content = strings.TrimSpace(content)
	content = strings.Trim(content, "` \t\r\n")
	content = strings.TrimPrefix(content, "知识卡片：")
	content = strings.TrimPrefix(content, "知识卡片:")
	content = strings.TrimPrefix(content, "正文：")
	content = strings.TrimPrefix(content, "正文:")
	content = strings.ReplaceAll(content, "\r\n", "\n")
	lines := strings.FieldsFunc(content, func(r rune) bool {
		return r == '\n' || r == '\r' || r == '\t'
	})
	content = strings.Join(lines, " ")
	content = strings.Join(strings.Fields(content), " ")
	return strings.TrimSpace(content)
}

// isQualityKnowledgeCard 检查字数 ≥80 且空泛表述 <3 条
func isQualityKnowledgeCard(content string) bool {
	if utf8.RuneCountInString(content) < minKnowledgeRunes {
		return false
	}
	weakPhrases := []string{"很重要", "非常重要", "很有用", "提升效率", "值得关注", "可以帮助", "广泛应用"}
	weakHits := 0
	for _, phrase := range weakPhrases {
		if strings.Contains(content, phrase) {
			weakHits++
		}
	}
	if weakHits >= 3 {
		return false
	}
	return strings.ContainsAny(content, "。；;：:，,")
}

func pushKnowledgeJSON(content, keyword string) {
	data, _ := json.Marshal(knowledgeData{Content: content, Keyword: keyword})
	evalJS(fmt.Sprintf(`if(window.updateKnowledgeFromGo) window.updateKnowledgeFromGo(%s)`, string(data)))
}

func fetchAndPushKnowledge() {
	cfg := loadConfig()
	keyword := cfg.KnowledgeKeyword
	if keyword == "" {
		return
	}

	var content string

	count := getKnowledgeCardCount(keyword)
	if count > 0 && rand.Intn(10) < 3 {
		content = getRandomKnowledgeCard(keyword)
	}

	if content == "" {
		content = fetchKnowledgeFromLLM(keyword, cfg)
		if content != "" {
			saveKnowledgeCard(keyword, content)
		}
	}

	if content == "" && count > 0 {
		content = getRandomKnowledgeCard(keyword)
	}

	if content == "" {
		pushKnowledgeJSON("知识卡片加载失败，稍后重试", keyword)
		return
	}

	pushKnowledgeJSON(content, keyword)
	preview := content
	if len(preview) > 30 {
		preview = preview[:30] + "..."
	}
	log.Println("知识卡片已推送:", preview)
}

func pushKnowledgeLoading(keyword string) {
	pushKnowledgeJSON("加载中...", keyword)
}

func pushKnowledgePlaceholder() {
	pushKnowledgeJSON("请设置知识关键字", "")
}

func knowledgeLoop() {
	initKnowledgeDB()

	cfg := loadConfig()
	if cfg.KnowledgeKeyword != "" && !cfg.HideKnowledge {
		if cached := getRandomKnowledgeCard(cfg.KnowledgeKeyword); cached != "" {
			pushKnowledgeJSON(cached, cfg.KnowledgeKeyword)
		} else {
			pushKnowledgeLoading(cfg.KnowledgeKeyword)
		}
	} else if cfg.KnowledgeKeyword == "" {
		pushKnowledgePlaceholder()
	}

	time.Sleep(3 * time.Second)

	cfg = loadConfig()
	if cfg.KnowledgeKeyword != "" && !cfg.HideKnowledge {
		fetchAndPushKnowledge()
	}

	ticker := time.NewTicker(30 * time.Minute)
	for range ticker.C {
		cfg := loadConfig()
		if cfg.KnowledgeKeyword != "" && !cfg.HideKnowledge {
			fetchAndPushKnowledge()
		}
	}
}

func triggerKnowledgeRefresh() {
	go fetchAndPushKnowledge()
}
