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

package brand

import (
	"context"
	"fmt"
	"strings"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	libkg "haixun-backend/internal/library/knowledge"
	liboutreach "haixun-backend/internal/library/outreach"
	libplacement "haixun-backend/internal/library/placement"
	libprompt "haixun-backend/internal/library/prompt"
	domai "haixun-backend/internal/model/ai/domain/usecase"
	aiusecase "haixun-backend/internal/model/ai/usecase"
	branddomain "haixun-backend/internal/model/brand/domain/usecase"
	outreachusecase "haixun-backend/internal/model/outreach_draft/domain/usecase"
	scanpostentity "haixun-backend/internal/model/scan_post/domain/entity"
	scanpostusecase "haixun-backend/internal/model/scan_post/domain/usecase"
	"haixun-backend/internal/svc"
	"haixun-backend/internal/types"

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

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

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

func (l *GenerateOutreachDraftsLogic) GenerateOutreachDrafts(req *types.GenerateOutreachDraftsHandlerReq) (resp *types.GenerateOutreachDraftsData, err error) {
	tenantID, uid, err := actorFrom(l.ctx)
	if err != nil {
		return nil, err
	}
	scanPostID := strings.TrimSpace(req.ScanPostID)
	count := 2
	if req.Count > 0 {
		count = req.Count
	}
	if scanPostID == "" {
		return nil, app.For(code.Brand).InputMissingRequired("scan_post_id is required")
	}
	if count > 4 {
		count = 4
	}

	brand, err := l.svcCtx.Brand.Get(l.ctx, tenantID, uid, req.ID)
	if err != nil {
		return nil, err
	}
	voicePersona := ""
	voiceID := strings.TrimSpace(req.VoicePersonaID)
	if voiceID != "" {
		vp, vpErr := l.svcCtx.Persona.Get(l.ctx, tenantID, uid, voiceID)
		if vpErr != nil {
			return nil, vpErr
		}
		voicePersona = vp.Persona
	}
	productID := strings.TrimSpace(req.ProductID)
	if productID == "" {
		productID = strings.TrimSpace(brand.ProductID)
	}
	post, err := l.svcCtx.ScanPost.Get(l.ctx, tenantID, uid, req.ID, scanPostID)
	if err != nil {
		return nil, err
	}

	topicLabel := strings.TrimSpace(post.SearchTag)
	placementReason := buildPlacementReason(post)
	if graph, graphErr := l.svcCtx.KnowledgeGraph.Get(l.ctx, tenantID, uid, req.ID); graphErr == nil && graph != nil {
		if node := findGraphNode(graph.Nodes, post.GraphNodeID); node != nil {
			if strings.TrimSpace(node.Label) != "" {
				topicLabel = strings.TrimSpace(node.Label)
			}
			placementReason = mergePlacementReason(placementReason, node, post)
		}
	}

	userPrompt, err := liboutreach.BuildUserPrompt(liboutreach.GenerateInput{
		Persona:         voicePersona,
		TopicLabel:      topicLabel,
		AudienceBrief:   brand.TargetAudience,
		ProductBrief:    productBriefForBrand(brand, productID, post.SearchTag),
		PlacementReason: placementReason,
		TargetText:      post.Text,
		AuthorName:      post.Author,
		Count:           count,
	})
	if err != nil {
		return nil, app.For(code.AI).SysInternal("outreach user prompt load failed")
	}

	systemPrompt, err := libprompt.OutreachPlacementSystem()
	if err != nil {
		return nil, app.For(code.AI).SysInternal("outreach system prompt load failed")
	}
	if strings.TrimSpace(voicePersona) != "" {
		systemPrompt = strings.TrimSpace(systemPrompt) + "\n\n人設與語氣：\n" + strings.TrimSpace(voicePersona)
	}

	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
	}

	result, err := l.svcCtx.AI.GenerateText(l.ctx, domai.GenerateRequest{
		Provider: providerID,
		Model:    credential.Model,
		Credential: domai.Credential{
			APIKey: credential.APIKey,
		},
		System: systemPrompt,
		Messages: []domai.Message{
			{Role: "user", Content: userPrompt},
		},
	})
	if err != nil {
		return nil, err
	}

	parsed, err := liboutreach.ParseGenerateOutput(result.Text)
	if err != nil {
		return nil, app.For(code.AI).SvcThirdParty("獲客留言 LLM 回傳無法解析：" + err.Error())
	}

	draftItems := make([]outreachusecase.DraftItem, 0, len(parsed.Drafts))
	for _, item := range parsed.Drafts {
		draftItems = append(draftItems, outreachusecase.DraftItem{
			Text:      item.Text,
			Angle:     item.Angle,
			Rationale: item.Rationale,
		})
	}
	saved, err := l.svcCtx.OutreachDraft.Create(l.ctx, outreachusecase.CreateRequest{
		TenantID:   tenantID,
		OwnerUID:   uid,
		BrandID:    req.ID,
		TopicID:    strings.TrimSpace(req.TopicID),
		ScanPostID: scanPostID,
		Relevance:  parsed.Relevance,
		Reason:     parsed.Reason,
		Drafts:     draftItems,
	})
	if err != nil {
		return nil, err
	}
	_, _ = l.svcCtx.ScanPost.UpdateOutreach(l.ctx, scanpostusecase.UpdateOutreachRequest{
		TenantID: tenantID,
		OwnerUID: uid,
		BrandID:  req.ID,
		PostID:   scanPostID,
		Status:   scanpostentity.OutreachStatusDrafted,
	})

	return toOutreachDraftData(saved), nil
}

func findGraphNode(nodes []libkg.Node, nodeID string) *libkg.Node {
	nodeID = strings.TrimSpace(nodeID)
	if nodeID == "" {
		return nil
	}
	for i := range nodes {
		if nodes[i].ID == nodeID {
			return &nodes[i]
		}
	}
	return nil
}

func buildPlacementReason(post *scanpostusecase.ScanPostSummary) string {
	if post == nil {
		return ""
	}
	parts := []string{}
	if post.Priority == "gold" {
		parts = append(parts, "雙軌命中（相關+近期）")
	} else if post.Priority == "recent" {
		parts = append(parts, "近期軌命中")
	}
	if post.SolvedByProduct {
		parts = append(parts, "產品可解此需求")
	}
	if post.ProductFitScore > 0 {
		parts = append(parts, fmt.Sprintf("產品匹配 %d", post.ProductFitScore))
	}
	return strings.Join(parts, "；")
}

func mergePlacementReason(base string, node *libkg.Node, post *scanpostusecase.ScanPostSummary) string {
	parts := []string{}
	if strings.TrimSpace(base) != "" {
		parts = append(parts, strings.TrimSpace(base))
	}
	if node != nil {
		if strings.TrimSpace(node.PlacementValue) != "" {
			parts = append(parts, "置入價值 "+strings.TrimSpace(node.PlacementValue))
		}
		if node.ProductFitScore > 0 && (post == nil || post.ProductFitScore == 0) {
			parts = append(parts, fmt.Sprintf("節點產品匹配 %d", node.ProductFitScore))
		}
	}
	return strings.Join(parts, "；")
}

func productBriefForBrand(brand *branddomain.BrandSummary, productID, searchTag string) string {
	if brand == nil {
		return ""
	}
	if product := pickProductForOutreach(brand, productID, searchTag); product != nil {
		merged := libplacement.BuildMergedProductContext(brand.DisplayName, product.ProductContext, product.Label)
		if pb := libplacement.ProductBriefFromContext(merged); pb != "" {
			return pb
		}
	}
	if pb := libplacement.ProductBriefFromContext(brand.ProductContext); pb != "" {
		return pb
	}
	return strings.TrimSpace(brand.ProductBrief)
}

func pickProductForOutreach(brand *branddomain.BrandSummary, productID, searchTag string) *branddomain.ProductSummary {
	if brand == nil || len(brand.Products) == 0 {
		return nil
	}
	if id := strings.TrimSpace(productID); id != "" {
		for i := range brand.Products {
			if brand.Products[i].ID == id {
				return &brand.Products[i]
			}
		}
	}
	tag := strings.TrimSpace(searchTag)
	if tag == "" {
		return &brand.Products[0]
	}
	bestScore := -1
	var best *branddomain.ProductSummary
	for i := range brand.Products {
		score := libplacement.ScoreProductForTag(tag, brand.Products[i].MatchTags)
		if score > bestScore {
			bestScore = score
			best = &brand.Products[i]
		}
	}
	if best != nil && bestScore > 0 {
		return best
	}
	return &brand.Products[0]
}

func toOutreachDraftData(saved *outreachusecase.DraftSummary) *types.GenerateOutreachDraftsData {
	if saved == nil {
		return nil
	}
	drafts := make([]types.OutreachDraftItemData, 0, len(saved.Drafts))
	for _, item := range saved.Drafts {
		drafts = append(drafts, types.OutreachDraftItemData{
			Text:      item.Text,
			Angle:     item.Angle,
			Rationale: item.Rationale,
		})
	}
	return &types.GenerateOutreachDraftsData{
		ID:         saved.ID,
		ScanPostID: saved.ScanPostID,
		Relevance:  saved.Relevance,
		Reason:     saved.Reason,
		Drafts:     drafts,
		CreateAt:   saved.CreateAt,
	}
}
