package personacopy

import (
	"context"
	"fmt"
	"strings"

	"haixun-backend/internal/library/copyvoice"
	libmatrix "haixun-backend/internal/library/matrix"
	"haixun-backend/internal/library/placement"
	"haixun-backend/internal/library/style8d"
	"haixun-backend/internal/library/websearch"
	domai "haixun-backend/internal/model/ai/domain/usecase"
	aiusecase "haixun-backend/internal/model/ai/usecase"
	contentopsdomain "haixun-backend/internal/model/content_ops/domain/usecase"
	copydraftentity "haixun-backend/internal/model/copy_draft/domain/entity"
	copydraftdomain "haixun-backend/internal/model/copy_draft/domain/usecase"
	personadomain "haixun-backend/internal/model/persona/domain/usecase"
	placementusecase "haixun-backend/internal/model/placement/usecase"
	threadsaccountdomain "haixun-backend/internal/model/threads_account/domain/usecase"
)

type TopicMatrixInput struct {
	TenantID      string
	OwnerUID      string
	PersonaID     string
	ContentPlanID string
	Topic         string
	Brief         string
	UseWebSearch  bool
	DraftCount    int
}

type TopicMatrixDeps struct {
	Persona        personadomain.UseCase
	CopyDraft      copydraftdomain.UseCase
	ContentOps     contentopsdomain.UseCase
	ThreadsAccount threadsaccountdomain.UseCase
	Placement      placementusecase.UseCase
	AI             domai.UseCase
}

type ProgressFn func(summary string, percentage int)

func RunTopicMatrix(ctx context.Context, deps TopicMatrixDeps, in TopicMatrixInput, progress ProgressFn) ([]copydraftdomain.CopyDraftSummary, error) {
	if progress == nil {
		progress = func(string, int) {}
	}
	topic := strings.TrimSpace(in.Topic)
	if topic == "" {
		return nil, fmt.Errorf("topic is required")
	}
	count := in.DraftCount
	if count <= 0 {
		count = 3
	}
	if count < 1 {
		count = 1
	}
	if count > 5 {
		count = 5
	}

	progress("讀取人設語氣…", 10)
	persona, err := deps.Persona.Get(ctx, in.TenantID, in.OwnerUID, in.PersonaID)
	if err != nil {
		return nil, err
	}
	credential, err := deps.ThreadsAccount.ResolveMemberAiCredential(ctx, in.TenantID, in.OwnerUID)
	if err != nil {
		return nil, err
	}
	providerID, err := aiusecase.MapWorkerProvider(credential.Provider)
	if err != nil {
		return nil, err
	}

	researchNotes := ""
	if in.UseWebSearch {
		progress("搜尋網路參考資料…", 25)
		researchNotes = searchTopicNotes(ctx, deps, in.TenantID, in.OwnerUID, topic, in.Brief)
	}
	personaBlock := style8d.ResolvePersonaBlock(persona.Persona, persona.StyleProfile, persona.Brief)
	feedbackNotes := recentFeedbackNotes(ctx, deps, in.TenantID, in.OwnerUID, in.PersonaID)
	knowledgeNotes := recentKnowledgeNotes(ctx, deps, in.TenantID, in.OwnerUID, in.PersonaID, topic)
	formulaNotes := formulaPoolNotes(ctx, deps, in.TenantID, in.OwnerUID, in.PersonaID)

	progress(fmt.Sprintf("呼叫 AI 產生 %d 篇草稿（%s / %s）…", count, credential.Provider, credential.Model), 45)
	parsed, err := libmatrix.GenerateCopyOutput(ctx, deps.AI, domai.GenerateRequest{
		Provider:   providerID,
		Model:      credential.Model,
		Credential: domai.Credential{APIKey: credential.APIKey},
		System:     TopicMatrixSystemPrompt(),
		Messages:   []domai.Message{{Role: "user", Content: TopicMatrixUserPrompt(topic, in.Brief, personaBlock, researchNotes, knowledgeNotes, formulaNotes, feedbackNotes, count)}},
	}, count)
	if err != nil {
		return nil, err
	}

	progress("寫入草稿…", 85)
	created := make([]copydraftdomain.CopyDraftSummary, 0, len(parsed.Rows))
	for _, row := range parsed.Rows {
		saved, saveErr := deps.CopyDraft.Create(ctx, copydraftdomain.CreateRequest{
			TenantID:            in.TenantID,
			OwnerUID:            in.OwnerUID,
			PersonaID:           in.PersonaID,
			ContentPlanID:       in.ContentPlanID,
			DraftType:           copydraftentity.DraftTypeMatrix,
			SortOrder:           row.SortOrder,
			Text:                row.Text,
			TopicTag:            row.SearchTag,
			Angle:               row.Angle,
			Hook:                row.Hook,
			Rationale:           row.Rationale,
			ReferenceNotes:      row.ReferenceNotes,
			Sources:             row.SourcePermalinks,
			AiScore:             row.AiScore,
			FormulaScore:        row.FormulaScore,
			BrandFitScore:       row.BrandFitScore,
			RiskScore:           row.RiskScore,
			SimilarityScore:     row.SimilarityScore,
			EngagementPotential: row.EngagementPotential,
			FreshnessScore:      row.FreshnessScore,
			ReviewSuggestion:    row.ReviewSuggestion,
		})
		if saveErr != nil {
			return created, saveErr
		}
		created = append(created, *saved)
	}
	progress(fmt.Sprintf("已產生 %d 篇草稿", len(created)), 100)
	return created, nil
}

func recentFeedbackNotes(ctx context.Context, deps TopicMatrixDeps, tenantID, ownerUID, personaID string) string {
	if deps.ContentOps == nil {
		return ""
	}
	items, err := deps.ContentOps.ListFeedbackEvents(ctx, tenantID, ownerUID, personaID, 8)
	if err != nil || len(items) == 0 {
		return ""
	}
	var b strings.Builder
	for _, item := range items {
		decision := strings.TrimSpace(item.Decision)
		note := strings.TrimSpace(item.Note)
		if decision == "" && note == "" {
			continue
		}
		b.WriteString("- ")
		if decision != "" {
			b.WriteString(decision)
		}
		if note != "" {
			b.WriteString("：")
			b.WriteString(note)
		}
		b.WriteString("\n")
	}
	return strings.TrimSpace(b.String())
}

func recentKnowledgeNotes(ctx context.Context, deps TopicMatrixDeps, tenantID, ownerUID, personaID, topic string) string {
	if deps.ContentOps == nil {
		return ""
	}
	items, err := deps.ContentOps.ListKnowledgeChunks(ctx, tenantID, ownerUID, personaID, 12)
	if err != nil {
		return ""
	}
	var b strings.Builder
	for _, item := range items {
		content := strings.TrimSpace(item.Content)
		if content == "" {
			continue
		}
		b.WriteString("- ")
		b.WriteString(shorten(content, 220))
		if item.RiskLevel != "" {
			b.WriteString("（風險：")
			b.WriteString(item.RiskLevel)
			b.WriteString("）")
		}
		b.WriteString("\n")
	}
	return strings.TrimSpace(b.String())
}

func formulaPoolNotes(ctx context.Context, deps TopicMatrixDeps, tenantID, ownerUID, personaID string) string {
	if deps.ContentOps == nil {
		return ""
	}
	items, err := deps.ContentOps.ListFormulaPools(ctx, tenantID, ownerUID, personaID, 12)
	if err != nil {
		return ""
	}
	var b strings.Builder
	for _, item := range items {
		b.WriteString("- ")
		b.WriteString(item.Type)
		b.WriteString(" / ")
		b.WriteString(item.Name)
		if item.Pattern != "" {
			b.WriteString("：")
			b.WriteString(item.Pattern)
		}
		if len(item.Avoid) > 0 {
			b.WriteString("；避免：")
			b.WriteString(strings.Join(item.Avoid, "、"))
		}
		b.WriteString("\n")
	}
	return strings.TrimSpace(b.String())
}

func shorten(raw string, max int) string {
	r := []rune(strings.TrimSpace(raw))
	if len(r) <= max {
		return string(r)
	}
	return string(r[:max]) + "..."
}

func searchTopicNotes(ctx context.Context, deps TopicMatrixDeps, tenantID, uid, topic, brief string) string {
	research, err := deps.Placement.ResearchSettings(ctx, tenantID, uid)
	if err != nil || !placement.WebSearchAvailable(research) {
		return ""
	}
	memberCtx, err := deps.ThreadsAccount.ResolveMemberPlacementContext(ctx, tenantID, uid, research)
	if err != nil {
		return ""
	}
	webClient := websearch.New(memberCtx.WebSearchConfig())
	if !webClient.Enabled() {
		return ""
	}
	resp, err := webClient.Search(ctx, websearch.SearchOptions{
		Query: strings.TrimSpace(topic + " " + brief),
		Limit: 5,
		Mode:  websearch.ModeKnowledgeExpand,
	})
	if err != nil || len(resp.Results) == 0 {
		return ""
	}
	var b strings.Builder
	for _, item := range resp.Results {
		if item.Title != "" {
			b.WriteString("- ")
			b.WriteString(item.Title)
			b.WriteString("\n")
		}
		if item.Snippet != "" {
			b.WriteString(item.Snippet)
			b.WriteString("\n")
		}
		if item.URL != "" {
			b.WriteString(item.URL)
			b.WriteString("\n")
		}
	}
	return strings.TrimSpace(b.String())
}

func TopicMatrixSystemPrompt() string {
	return strings.TrimSpace(`你是 Threads / Instagram 貼文編輯。請依人設語言指紋產出自然、可直接發布的繁體中文貼文矩陣。

` + copyvoice.SystemRules() + `

規則：
- 不要每篇都用同一套 hook / 三段式 / 結尾問句。
- 不要企業八股、不要像教科書、不要一直喊口號。
- 人設定位決定誰在說，語言指紋決定怎麼說；必須遵守段落節奏、標點換行、知識轉譯與 CTA 習慣。
- 不要把人設介紹裡的物件/標籤硬寫進正文情境。例：人設寫 Y2K、咖啡、穿搭，只代表語感與審美，不代表每篇都要出現老歌、咖啡、穿搭、MV。
- 正文內容必須由【話題】與【補充】決定；人設不得蓋過主題。
- 每篇要有不同角度；長度與格式依人設習慣和主題型態決定，不要強制短文。
- 每次至少產出 3 種版本思路：穩定帳號風格、更有共鳴、更容易互動；若 count 超過 3，再補更短或更反差版本。
- text 只能放貼文正文，不要放標題、分析、公式、模板或說明。
- text 排版：像真人手機發文；可使用自然標點、空行、列點與少量 emoji。不要完全無標點，也不要每句硬換行，除非人設樣本就是這樣。
- 只回傳 JSON 物件：{"rows":[...]}。
- 每筆 row 必須包含 sort_order, search_tag, angle, hook, text, reference_notes, source_permalinks, rationale, ai_score, formula_score, brand_fit_score, risk_score, similarity_score, engagement_potential, freshness_score, review_suggestion。
- 分數欄位都是 0-100 整數：ai_score / formula_score / risk_score / similarity_score 越低越好；brand_fit_score / engagement_potential / freshness_score 越高越好。
- review_suggestion 只能是「可通過」「需人工確認」「建議重寫」之一。
- rationale 必須用繁中短列點包含：版本定位、AI感分數(0-100，越高越AI)、公式感分數(0-100)、人設符合度(0-100)、風險分數(0-100)、互動潛力(0-100)、審核建議（可通過/需人工確認/建議重寫）與原因。`)
}

func TopicMatrixUserPrompt(topic, brief, personaBlock, researchNotes, knowledgeNotes, formulaNotes, feedbackNotes string, count int) string {
	var b strings.Builder
	b.WriteString("請產生 ")
	b.WriteString(fmt.Sprintf("%d", count))
	b.WriteString(" 篇 Threads 草稿。\n\n【話題】\n")
	b.WriteString(strings.TrimSpace(topic))
	if brief = strings.TrimSpace(brief); brief != "" {
		b.WriteString("\n\n【補充】\n")
		b.WriteString(brief)
	}
	if personaBlock = strings.TrimSpace(personaBlock); personaBlock != "" {
		b.WriteString("\n\n【人設定位、語言指紋與禁忌（最高優先）】\n")
		b.WriteString(personaBlock)
		b.WriteString("\n\n")
		b.WriteString(copyvoice.PersonaCalibrationNote())
	}
	if researchNotes = strings.TrimSpace(researchNotes); researchNotes != "" {
		b.WriteString("\n\n【網路資料參考】\n")
		b.WriteString(researchNotes)
	}
	if knowledgeNotes = strings.TrimSpace(knowledgeNotes); knowledgeNotes != "" {
		b.WriteString("\n\n【Knowledge Memory：帳號知識來源，優先採用但不要硬塞】\n")
		b.WriteString(knowledgeNotes)
	}
	if formulaNotes = strings.TrimSpace(formulaNotes); formulaNotes != "" {
		b.WriteString("\n\n【Formula Pool：可選公式池，請依 Content Plan 選擇，不要每篇都套同一套】\n")
		b.WriteString(formulaNotes)
	}
	if feedbackNotes = strings.TrimSpace(feedbackNotes); feedbackNotes != "" {
		b.WriteString("\n\n【最近人工審核回饋（Feedback Memory，必須吸收）】\n")
		b.WriteString(feedbackNotes)
	}
	b.WriteString("\n\n【品質閘門】\n請先自我檢查 AI 感、公式感、事實風險、人設一致性、與過去常見句型重複。高風險內容不得建議直接通過。")
	b.WriteString("\n\n請讓每篇的 angle 明確不同，search_tag 是短主題標籤不含 #。source_permalinks 可放參考 URL，沒有就空陣列。")
	return b.String()
}
