package placement

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"
	"unicode/utf8"
)

type ResearchMap struct {
	AudienceSummary string   `json:"audienceSummary"`
	ContentGoal     string   `json:"contentGoal"`
	Questions       []string `json:"questions"`
	Pillars         []string `json:"pillars"`
	Exclusions      []string `json:"exclusions"`
	PatrolKeywords  []string `json:"patrolKeywords"`
}

type ResearchMapInput struct {
	BrandDisplayName string
	TopicName        string
	SeedQuery        string
	Brief            string
	Goals            string
	TargetAudience   string
	ProductContext   string
	ProductBrief     string
	ProductLabel     string
}

const (
	minResearchQuestions  = 8
	minResearchPillars    = 6
	minResearchExclusions = 8
	minAudienceRunes      = 80
	minContentGoalRunes   = 50
)

var researchMapFenceRE = regexp.MustCompile("(?s)```(?:json)?\\s*([\\s\\S]*?)```")

func BuildResearchMapAnalysisSystemPrompt() string {
	return strings.TrimSpace(`你是 Threads（脆）產品置入研究顧問。請先閱讀使用者提供的**品牌、置入產品、主題目標**等輸入，完成結構化分析。

## 任務
- 整合所有輸入，想清楚受眾是誰、他們在煩什麼、產品能解決什麼、什麼貼文值得留言置入
- **本步驟只輸出分析文字，不要輸出 JSON**

## 請依序回答（每段 2～4 句，繁體中文，具體可執行）
1. **品牌與產品**：這個品牌／產品能解決什麼具體困擾？關鍵賣點是什麼？
2. **受眾輪廓**：誰、在什麼生活／治療情境、會因什麼症狀發文求助？
3. **求助句型方向**：Threads 上可能出現哪些求助句（列 6～10 個方向，尚未定稿）
4. **內容方向與排除**：適合找文的支柱主題、必須排除的貼文類型（排除項只寫內容類型，不寫發文時間）
5. **置入策略**：contentGoal 應點名的完整產品名稱、近期發文窗口、留言置入時機

分析必須緊扣輸入資料，不可套用與品牌／產品／主題無關的通用範例。`)
}

func BuildResearchMapAnalysisUserPrompt(in ResearchMapInput) string {
	var b strings.Builder
	b.WriteString("以下為本次置入主題的完整輸入，請先分析：\n\n")
	PlacementTopicContext{
		BrandDisplayName: in.BrandDisplayName,
		TopicName:        in.TopicName,
		SeedQuery:        in.SeedQuery,
		Brief:            in.Brief,
		Goals:            in.Goals,
		TargetAudience:   in.TargetAudience,
		ProductContext:   in.ProductContext,
		ProductBrief:     in.ProductBrief,
		ProductLabel:     in.ProductLabel,
	}.WritePromptBlock(&b)
	return b.String()
}

func BuildResearchMapFinalizeUserPrompt(in ResearchMapInput, analysis string) string {
	var b strings.Builder
	b.WriteString("【輸入資料】\n")
	PlacementTopicContext{
		BrandDisplayName: in.BrandDisplayName,
		TopicName:        in.TopicName,
		SeedQuery:        in.SeedQuery,
		Brief:            in.Brief,
		Goals:            in.Goals,
		TargetAudience:   in.TargetAudience,
		ProductContext:   in.ProductContext,
		ProductBrief:     in.ProductBrief,
		ProductLabel:     in.ProductLabel,
	}.WritePromptBlock(&b)
	analysis = strings.TrimSpace(analysis)
	if analysis != "" {
		b.WriteString("\n【前置分析】\n")
		b.WriteString(analysis)
		b.WriteString("\n")
	}
	b.WriteString(`
---

請依【前置分析】與【輸入資料】產出**完整**研究地圖 JSON（不可精簡），密度請對齊系統 prompt 中的優秀範例：
1. 每個欄位必須反映本次品牌、產品與使用者輸入，不可照搬範例的主題用字
2. audienceSummary 寫清楚「誰、治療／生活情境、具體困擾、在找什麼產品特徵」
3. contentGoal 要寫入【產品詳情】的完整產品名稱、近期發文、留言置入時機
4. questions 至少 8 句、pillars 至少 6 句、exclusions 至少 8 句（exclusions 只寫貼文類型，**不要寫時間／近期／幾天內**）
5. questions 是「人會發文的求助句」；patrolKeywords 是「人會打進 Threads 搜尋框的短句」，兩者不可混在一起
6. patrolKeywords 至少 6 組、最多 8 組，每組 6～16 字，從 questions 提煉搜尋短句，必須可直接拿去搜尋`)
	return b.String()
}

func BuildResearchMapSystemPrompt() string {
	return strings.TrimSpace(`你是 Threads（脆）產品置入研究顧問。目標是幫品牌找到「近期發文、作者有需求、現在留言還來得及自然推薦產品」的貼文——不是找爆款來模仿發文。

## 工作流程
- 你會收到【輸入資料】（品牌、產品、主題目標等）與【前置分析】
- 請先理解分析與輸入的對應關係，再產出研究地圖 JSON
- 產出必須緊扣本次品牌與產品，不可套用與輸入無關的通用內容

## 產出密度（重要）
- 這份研究地圖要給真人閱讀與執行，**寧可詳盡也不要過度精簡**
- 每個欄位都要寫滿規格下限；不可用單一關鍵字、標籤語或一句話敷衍
- 只回傳一個 JSON 物件，不要 markdown 說明

## 核心原則
1. questions 與 pillars 會直接拿去 Threads 找貼文——這兩項是最重要的產出
2. 置入時間窗口最重要：優先「近期」求助帖，作者有困擾、留言區還能自然推薦
3. 海巡要能盡量找到可置入貼文，但寧可少給也不要跑題
4. 複合主題不可拆成過寬單字（寵物洗毛精 ≠ 只搜「寵物」「狗狗」）
5. 全部繁體中文，貼近台灣 Threads 使用者口語

## audienceSummary（3～5 句，至少 80 字）
- 具體描述：誰、生活情境、正在煩什麼、為什麼會發文求助、跟產品有什麼關係
- 不要寫「注重保養的消費者」這類空泛句

## contentGoal（2～3 句，至少 50 字）
- 明確寫：找到「近期發文（理想 3 天內）」、作者本身有產品可解決的需求、留言區還能自然推薦的貼文
- **必須寫入【產品置入】中的產品名稱**（含品類與關鍵賣點，如「抗敏無香沐浴露」），不可用「目標產品」等泛稱代替
- 強調「現在就能留言置入還來得及且不突兀」，不是模仿發文或內容企劃

## questions（至少 8 個，每句 8～24 字）
- 像真人會在 Threads 打的求助句，帶治療階段、困擾、求推薦、請益、經驗分享
- 要涵蓋不同角度（症狀、能不能用、品牌推薦、挑選經驗、康復後換品）
- 差：「癌症保養」「沐浴乳」這種單一關鍵字

## pillars（至少 6 個，每句 6～22 字）
- 允許的內容方向，用來找貼文並過濾結果；要比 questions 更偏主題詞組
- 差：「保養」「健康」這種過寬詞

## exclusions（至少 8 個，每句 8～28 字）
- 觸及即排除的**貼文類型／內容方向**；要寫清楚「為什麼不能置入」（純晒照、業配、無求助、跑題癌別、錯品類、已滿意他牌、非病友視角等）
- **禁止寫時間相關條件**：不要碰不是篩發文時間用的。不可出現「過舊貼文」「非近期發文」「3 天前」「一週以上」「發文太久」等——置入時間窗口只寫在 contentGoal

## patrolKeywords（6～8 組，每組 6～14 字）
- 這不是分類標籤，而是**真人會貼進 Threads 搜尋框的短句**
- 每組 2～4 個詞，空格分隔；必須同時保留「情境／困擾」與「產品品類／用途」
- 優先格式：「困擾 品類」、「族群 品類 推薦」、「症狀 品類 請問」，例如「化療 沐浴乳 推薦」「無香 洗衣精 請問」
- 要能找到成果：過短、過廣、太像標籤的不要給（差：「癌症」「沐浴」「健康生活」「環境荷爾蒙」）
- 不要整句複製 questions，也不要寫品牌名；搜尋句要像使用者會查「有沒有人在討論」的字

## 優秀範例（請接近此密度、具體度與欄位長度）
{
  "audienceSummary": "因荷爾蒙相關癌症（如乳癌、婦科癌症）正在治療中或康復後，對香精與化學成分特別敏感，洗澡或清潔時容易因香味而噁心、頭痛或皮膚不適，因此積極尋找「完全無香、抗敏、有第三方認證」沐浴與清潔產品的人。",
  "contentGoal": "找出「近期發文（理想 3 天內）」、作者本身因癌症或荷爾蒙治療導致對香味／化學成分敏感、有明確換沐浴乳或清潔產品需求、現在留言區自然推薦 ecostore 抗敏無香沐浴露還來得及且不突兀的貼文。",
  "questions": [
    "化療後皮膚敏感要換什麼沐浴乳",
    "乳癌治療中不能用有香味的沐浴乳嗎",
    "癌症病人適合用的無香沐浴乳推薦",
    "荷爾蒙治療皮膚乾癢怎麼挑沐浴乳",
    "打標靶後對香味很敏感怎麼辦",
    "康復後不想再用有香精的清潔用品",
    "癌症病友都用什麼牌子沐浴乳",
    "化療期間沐浴乳挑選經驗分享"
  ],
  "pillars": [
    "化療皮膚敏感無香沐浴乳",
    "乳癌病友沐浴用品挑選",
    "荷爾蒙治療對香味敏感",
    "癌症康復後換清潔品牌",
    "抗敏無香沐浴乳推薦",
    "化療期間皮膚照護"
  ],
  "exclusions": [
    "純曬照、純分享日常生活的貼文",
    "沒有求助、沒有換產品需求的閒聊",
    "只談癌症治療、確診心情但未提及清潔／沐浴困擾",
    "推廣其他品牌沐浴乳或業配他牌的貼文",
    "男性攝護腺癌、肺癌等與香味敏感較無直接相關的癌別（除非明確提到化學敏感）",
    "寵物洗毛精、洗碗精等其他品類",
    "已使用 ecostore 或他牌抗敏沐浴乳且滿意、無換品牌需求的貼文",
    "醫療專業衛教、營養師發文等非病友視角貼文"
  ],
  "patrolKeywords": [
    "化療 沐浴乳 推薦",
    "無香沐浴乳 請問",
    "乳癌 沐浴乳 推薦",
    "荷爾蒙 敏感 沐浴乳",
    "化療 皮膚 沐浴乳",
    "癌症 無香 沐浴乳",
    "病友 沐浴乳 推薦",
    "抗敏 沐浴乳 推薦"
  ]
}`)
}

// BuildResearchMapUserPrompt is kept for tests; production uses analysis + finalize prompts.
func BuildResearchMapUserPrompt(in ResearchMapInput) string {
	return BuildResearchMapFinalizeUserPrompt(in, "")
}

func ResearchMapRetryUserPrompt() string {
	return strings.TrimSpace(`上次產出過於簡略或項目不足。請重新產出完整 JSON，密度對齊系統 prompt 優秀範例：
- 必須緊扣【輸入資料】中的品牌、產品與主題目標，不可照搬範例用字
- audienceSummary ≥80 字，contentGoal ≥50 字（contentGoal 要寫入完整產品名稱）
- questions ≥8、pillars ≥6、exclusions ≥8（exclusions 不可含時間條件）
- patrolKeywords 要像真人會在 Threads 搜尋框打的 2～4 詞短句，必須同時包含困擾與品類
- 每句都要具體、可執行，不要用單一關鍵字敷衍`)
}

func ParseResearchMapOutput(raw string) (ResearchMap, error) {
	payload, err := extractResearchJSONObject(raw)
	if err != nil {
		return ResearchMap{}, err
	}
	var out ResearchMap
	if err := json.Unmarshal(payload, &out); err != nil {
		return ResearchMap{}, fmt.Errorf("parse research map json: %w", err)
	}
	out.AudienceSummary = strings.TrimSpace(out.AudienceSummary)
	out.ContentGoal = strings.TrimSpace(out.ContentGoal)
	out.Questions = cleanStringList(out.Questions)
	out.Pillars = cleanStringList(out.Pillars)
	out.Exclusions = filterTimeExclusions(cleanStringList(out.Exclusions))
	out.PatrolKeywords = cleanStringList(out.PatrolKeywords)
	if out.AudienceSummary == "" && len(out.Questions) == 0 {
		return ResearchMap{}, fmt.Errorf("research map missing audience or questions")
	}
	return out, nil
}

func ResearchMapTooThin(m ResearchMap) bool {
	if utf8.RuneCountInString(m.AudienceSummary) < minAudienceRunes {
		return true
	}
	if utf8.RuneCountInString(strings.TrimSpace(m.ContentGoal)) < minContentGoalRunes {
		return true
	}
	if len(m.Questions) < minResearchQuestions {
		return true
	}
	if len(m.Pillars) < minResearchPillars {
		return true
	}
	if len(m.Exclusions) < minResearchExclusions {
		return true
	}
	return false
}

var exclusionTimePattern = regexp.MustCompile(`近期|過舊|太久|天內|天前|幾天|一週|上週|發文時間|非近期|多久以前|月份前|昨天|今天發文|時間窗口|理想\s*\d`)

func filterTimeExclusions(items []string) []string {
	if len(items) == 0 {
		return items
	}
	out := make([]string, 0, len(items))
	for _, item := range items {
		if exclusionTimePattern.MatchString(item) {
			continue
		}
		out = append(out, item)
	}
	return out
}

func cleanStringList(items []string) []string {
	out := make([]string, 0, len(items))
	seen := map[string]struct{}{}
	for _, item := range items {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		if _, ok := seen[item]; ok {
			continue
		}
		seen[item] = struct{}{}
		out = append(out, item)
	}
	return out
}

func extractResearchJSONObject(raw string) ([]byte, error) {
	raw = strings.TrimSpace(raw)
	if m := researchMapFenceRE.FindStringSubmatch(raw); len(m) == 2 {
		raw = strings.TrimSpace(m[1])
	}
	start := strings.Index(raw, "{")
	end := strings.LastIndex(raw, "}")
	if start < 0 || end <= start {
		return nil, fmt.Errorf("research map output missing json object")
	}
	return []byte(raw[start : end+1]), nil
}

func ResearchMapTopicLabel(brandDisplayName, topicName string) string {
	if topic := strings.TrimSpace(topicName); topic != "" {
		return topic
	}
	return strings.TrimSpace(brandDisplayName)
}
