// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1

package copy_mission

import (
	"context"
	"strings"
	"time"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	"haixun-backend/internal/library/placement"
	"haixun-backend/internal/library/style8d"
	libviral "haixun-backend/internal/library/viral"
	"haixun-backend/internal/library/websearch"
	domai "haixun-backend/internal/model/ai/domain/usecase"
	aiusecase "haixun-backend/internal/model/ai/usecase"
	contentops "haixun-backend/internal/model/content_ops/domain/usecase"
	personadomain "haixun-backend/internal/model/persona/domain/usecase"
	"haixun-backend/internal/svc"
	"haixun-backend/internal/types"

	"github.com/zeromicro/go-zero/core/logx"
)

type InspireCopyMissionLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewInspireCopyMissionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *InspireCopyMissionLogic {
	return &InspireCopyMissionLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *InspireCopyMissionLogic) InspireCopyMission(req *types.CopyMissionInspirationHandlerReq) (resp *types.CopyMissionInspirationData, err error) {
	tenantID, uid, err := actorFrom(l.ctx)
	if err != nil {
		return nil, err
	}
	personaID := strings.TrimSpace(req.PersonaID)
	if personaID == "" {
		return nil, app.For(code.Persona).InputMissingRequired("persona_id is required")
	}
	persona, err := l.svcCtx.Persona.Get(l.ctx, tenantID, uid, personaID)
	if err != nil {
		return nil, err
	}
	credential, err := l.svcCtx.ThreadsAccount.ResolveMemberAiCredential(l.ctx, tenantID, uid)
	if err != nil {
		return nil, err
	}
	providerID, err := aiusecase.MapWorkerProvider(credential.Provider)
	if err != nil {
		return nil, err
	}

	keyword := strings.TrimSpace(req.Keyword)
	trends, sources, searchUsed := []libviral.MissionInspireTrendSnippet(nil), []types.CopyMissionInspirationSourceData(nil), false
	if req.UseWebSearch {
		trends, sources, searchUsed = l.collectTrendSignals(tenantID, uid, persona, keyword)
	}
	llmOnly := len(trends) == 0
	if keyword != "" && llmOnly {
		trends = append(trends, libviral.MissionInspireTrendSnippet{
			Query:   keyword,
			Title:   "使用者指定方向",
			Snippet: keyword,
		})
	}

	personaBlock := style8d.ResolvePersonaBlock(persona.Persona, persona.StyleProfile, persona.Brief)
	temp := 0.72
	result, err := l.svcCtx.AI.GenerateText(l.ctx, domai.GenerateRequest{
		Provider:    providerID,
		Model:       credential.Model,
		Credential:  domai.Credential{APIKey: credential.APIKey},
		System:      libviral.BuildMissionInspireSystemPrompt(),
		Temperature: &temp,
		Messages: []domai.Message{{Role: "user", Content: libviral.BuildMissionInspireUserPrompt(libviral.MissionInspireInput{
			PersonaDisplayName: persona.DisplayName,
			PersonaBrief:       persona.Brief,
			PersonaBlock:       personaBlock,
			StyleBenchmark:     persona.StyleBenchmark,
			AvoidTopics:        cleanAvoidTopics(req.AvoidTopics),
			ContentDirection:   req.ContentDirection,
			UserKeyword:        keyword,
			TrendSnippets:      trends,
			WebSearchProvider:  sourceLabel(searchUsed, sources),
			LLMOnly:            llmOnly,
		})}},
	})
	if err != nil {
		return nil, app.For(code.AI).SvcThirdParty("起號靈感生成失敗：" + err.Error())
	}
	parsed, err := libviral.ParseMissionInspireOutput(result.Text)
	if err != nil {
		return nil, app.For(code.AI).SvcThirdParty("起號靈感回傳無法解析：" + err.Error())
	}

	topicCandidate, plan := l.persistInspiration(tenantID, uid, personaID, parsed, searchUsed)
	return &types.CopyMissionInspirationData{
		TopicCandidateID: topicCandidate,
		ContentPlanID:    plan,
		Label:            parsed.Label,
		SeedQuery:        parsed.SeedQuery,
		Brief:            parsed.Brief,
		TrendReason:      parsed.TrendReason,
		TrendKeywords:    parsed.TrendKeywords,
		Angles:           parsed.Angles,
		Mission:          parsed.Mission,
		TargetAudience:   parsed.TargetAudience,
		OpeningType:      parsed.OpeningType,
		BodyType:         parsed.BodyType,
		Emotion:          parsed.Emotion,
		CtaType:          parsed.CtaType,
		RiskLevel:        parsed.RiskLevel,
		Avoid:            parsed.Avoid,
		Sources:          sources,
		WebSearchUsed:    searchUsed,
		Message:          "已找到一個可延伸的話題與內容計畫",
	}, nil
}

func (l *InspireCopyMissionLogic) persistInspiration(tenantID, uid, personaID string, parsed libviral.MissionInspireOutput, searchUsed bool) (string, string) {
	if l.svcCtx.ContentOps == nil {
		return "", ""
	}
	riskScore := riskScoreFromLevel(parsed.RiskLevel)
	category := parsed.Mission
	if category == "" {
		category = "候選話題"
	}
	source := "llm"
	if searchUsed {
		source = "topic_radar"
	}
	topic, err := l.svcCtx.ContentOps.CreateTopicCandidate(l.ctx, contentops.TopicCandidateInput{
		TenantID:            tenantID,
		OwnerUID:            uid,
		PersonaID:           personaID,
		Name:                parsed.Label,
		Source:              source,
		Category:            category,
		SeedQuery:           parsed.SeedQuery,
		TrendReason:         parsed.TrendReason,
		TrendKeywords:       parsed.TrendKeywords,
		HeatScore:           scoreBySearch(searchUsed),
		FitScore:            82,
		InteractionScore:    70,
		ExtendScore:         78,
		RiskScore:           riskScore,
		RecommendedMissions: []string{parsed.Mission},
	})
	if err != nil {
		l.Errorf("persist topic candidate: %v", err)
		return "", ""
	}
	angle := ""
	if len(parsed.Angles) > 0 {
		angle = parsed.Angles[0]
	}
	plan, err := l.svcCtx.ContentOps.CreateContentPlan(l.ctx, contentops.ContentPlanInput{
		TenantID:            tenantID,
		OwnerUID:            uid,
		PersonaID:           personaID,
		TopicCandidateID:    topic.ID,
		Topic:               parsed.Label,
		Mission:             fallbackString(parsed.Mission, "互動文"),
		TargetAudience:      parsed.TargetAudience,
		Angle:               angle,
		OpeningType:         parsed.OpeningType,
		BodyType:            parsed.BodyType,
		Emotion:             parsed.Emotion,
		CtaType:             parsed.CtaType,
		RiskLevel:           parsed.RiskLevel,
		RequiresHumanReview: parsed.RiskLevel != "low",
		Avoid:               parsed.Avoid,
	})
	if err != nil {
		l.Errorf("persist content plan: %v", err)
		return topic.ID, ""
	}
	return topic.ID, plan.ID
}

func scoreBySearch(searchUsed bool) int {
	if searchUsed {
		return 76
	}
	return 55
}

func riskScoreFromLevel(level string) int {
	switch strings.ToLower(strings.TrimSpace(level)) {
	case "high":
		return 75
	case "low":
		return 20
	default:
		return 45
	}
}

func fallbackString(value, fallback string) string {
	if strings.TrimSpace(value) != "" {
		return strings.TrimSpace(value)
	}
	return fallback
}

func (l *InspireCopyMissionLogic) collectTrendSignals(tenantID, uid string, persona *personadomain.PersonaSummary, keyword string) ([]libviral.MissionInspireTrendSnippet, []types.CopyMissionInspirationSourceData, bool) {
	if persona == nil {
		return nil, nil, false
	}
	trends := make([]libviral.MissionInspireTrendSnippet, 0, 12)
	seen := map[string]struct{}{}
	addTrend := func(item libviral.MissionInspireTrendSnippet) {
		item.Query = strings.TrimSpace(item.Query)
		item.Title = strings.TrimSpace(item.Title)
		item.Snippet = strings.TrimSpace(item.Snippet)
		item.URL = strings.TrimSpace(item.URL)
		if item.Title == "" && item.Snippet == "" {
			return
		}
		if isNewsLikeInspiration(item.Title, item.Snippet, item.URL) {
			return
		}
		key := strings.ToLower(item.Query + "|" + item.Title + "|" + item.Snippet)
		if _, ok := seen[key]; ok {
			return
		}
		seen[key] = struct{}{}
		trends = append(trends, item)
	}

	for _, item := range libviral.CollectFreeTopicTrends(l.ctx, keyword, persona.Brief) {
		addTrend(item)
	}

	research, err := l.svcCtx.Placement.ResearchSettings(l.ctx, tenantID, uid)
	if err != nil {
		sources := inspirationSourcesFromTrends(trends)
		return trends, sources, len(sources) > 0
	}
	memberCtx, err := l.svcCtx.ThreadsAccount.ResolveMemberPlacementContext(l.ctx, tenantID, uid, research)
	if err != nil {
		sources := inspirationSourcesFromTrends(trends)
		return trends, sources, len(sources) > 0
	}

	if placement.WebSearchAvailable(research) {
		client := websearch.New(memberCtx.WebSearchConfig())
		for _, item := range libviral.CollectMissionInspireTrends(l.ctx, client, memberCtx, strings.TrimSpace(persona.Brief+" "+keyword), persona.StyleBenchmark) {
			addTrend(item)
		}
		if keyword != "" && client.Enabled() {
			for _, query := range []string{
				"Threads 台灣 " + keyword + " 熱門 關鍵字 討論",
				keyword + " 請問 心得 推薦 Threads",
				keyword + " 痛點 經驗 怎麼辦 社群",
				keyword + " Dcard PTT Threads 熱門 討論",
			} {
				res, searchErr := client.Search(l.ctx, websearch.SearchOptions{
					Query:              query,
					Limit:              4,
					Mode:               websearch.ModeKnowledgeExpand,
					Country:            memberCtx.BraveCountry,
					SearchLang:         memberCtx.BraveSearchLang,
					UserLocation:       memberCtx.ExaUserLocation,
					StartPublishedDate: placement.FormatPublishedAfterISO(14, time.Now().UTC()),
				})
				if searchErr != nil || res.Status != "success" {
					continue
				}
				for _, result := range res.Results {
					addTrend(libviral.MissionInspireTrendSnippet{
						Query:   query,
						Title:   result.Title,
						Snippet: result.Snippet,
						URL:     result.URL,
					})
				}
			}
		}
	}

	if keyword != "" && len(trends) < 8 {
		for _, item := range libviral.CollectMissionInspireThreadsTrends(l.ctx, libviral.MissionInspireThreadsInput{
			Keyword:        keyword,
			PersonaBrief:   persona.Brief,
			StyleBenchmark: persona.StyleBenchmark,
			Pillars:        persona.CopyResearchMap.Pillars,
			Questions:      persona.CopyResearchMap.Questions,
			Member:         memberCtx,
		}) {
			addTrend(item)
		}
	}

	if len(trends) > 10 {
		trends = trends[:10]
	}
	sources := inspirationSourcesFromTrends(trends)
	return trends, sources, len(sources) > 0
}

func isNewsLikeInspiration(title, snippet, url string) bool {
	text := strings.ToLower(title + " " + snippet + " " + url)
	newsSignals := []string{"新聞", "快訊", "中央社", "聯合新聞", "ettoday", "tvbs", "setn", "ctwant", "chinatimes", "ltn.com", "news"}
	socialSignals := []string{"threads", "dcard", "ptt", "請問", "心得", "推薦", "有人", "怎麼辦", "討論", "留言"}
	newsScore := 0
	for _, signal := range newsSignals {
		if strings.Contains(text, strings.ToLower(signal)) {
			newsScore++
		}
	}
	if newsScore == 0 {
		return false
	}
	for _, signal := range socialSignals {
		if strings.Contains(text, strings.ToLower(signal)) {
			return false
		}
	}
	return true
}

func inspirationSourcesFromTrends(trends []libviral.MissionInspireTrendSnippet) []types.CopyMissionInspirationSourceData {
	sources := make([]types.CopyMissionInspirationSourceData, 0, len(trends))
	for _, item := range trends {
		sources = append(sources, types.CopyMissionInspirationSourceData{
			Query:   item.Query,
			Title:   item.Title,
			Snippet: item.Snippet,
			URL:     item.URL,
		})
	}
	return sources
}

func sourceLabel(used bool, sources []types.CopyMissionInspirationSourceData) string {
	if !used || len(sources) == 0 {
		return ""
	}
	return "Free Trend / Web / Threads Search"
}

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