package usecase

import (
	"fmt"
	"strings"

	"apps/backend/internal/module/radar/domain"
)

// ScoreProductFit evaluates one public candidate against one immutable product
// snapshot. It never treats a brand/product name as demand evidence.
func ScoreProductFit(cand *domain.CandidatePost, product *ProductContextSnapshot) (*domain.ProductMatch, error) {
	if cand == nil {
		return nil, fmt.Errorf("%w: candidate required", domain.ErrValidation)
	}
	if product == nil || strings.TrimSpace(product.BrandID) == "" || strings.TrimSpace(product.ProductID) == "" {
		return nil, fmt.Errorf("%w: product context required", domain.ErrValidation)
	}
	corpus := strings.TrimSpace(cand.Text)
	if strings.TrimSpace(cand.Title) != "" {
		if corpus != "" {
			corpus += " "
		}
		corpus += strings.TrimSpace(cand.Title)
	}

	type dimensionInput struct {
		dimension string
		terms     []string
		zero      string
	}
	inputs := []dimensionInput{
		{domain.ProductFitPain, product.PainPoints, "貼文沒有明確提到產品痛點"},
		{domain.ProductFitScenario, append(append([]string(nil), product.MatchTags...), product.ProductContext), "貼文沒有明確提到使用或購買情境"},
		{domain.ProductFitAudience, []string{product.TargetAudience}, "貼文沒有明確支持目標受眾，維持 0 分"},
		{domain.ProductFitCapability, product.ProviderCapabilityTerms, "貼文沒有出現可由產品處理的能力詞"},
	}
	reasons := make([]domain.ProductFitReason, 0, len(inputs))
	matchedTerms := make([]string, 0, 4)
	scores := make(map[string]int, len(inputs))
	for _, input := range inputs {
		basis, excerpt := firstProductEvidence(corpus, input.terms, product)
		score := 0
		reason := input.zero
		if basis != "" && excerpt != "" {
			score = domain.ProductFitDimensionWeight(input.dimension)
			reason = fmt.Sprintf("貼文片段「%s」對應產品%s「%s」", excerpt, productDimensionLabel(input.dimension), basis)
			matchedTerms = append(matchedTerms, basis)
		}
		scores[input.dimension] = score
		reasons = append(reasons, domain.ProductFitReason{
			Dimension: input.dimension, Score: score, Reason: reason,
			CandidateExcerpt: excerpt, ProductBasis: basis,
		})
	}

	excluded, excludeReason := productCandidateExclusion(corpus, cand, product)
	risks := make([]string, 0, 2)
	if excluded {
		risks = append(risks, excludeReason)
	}
	if scores[domain.ProductFitAudience] == 0 {
		risks = append(risks, "受眾證據不足，未猜測貼文作者身分")
	}
	if scores[domain.ProductFitPain] == 0 && scores[domain.ProductFitScenario] == 0 {
		risks = append(risks, "只有產品／品牌名稱或搜尋詞命中，不能視為需求證據")
	}
	total := 0
	for _, reason := range reasons {
		total += reason.Score
	}
	match := &domain.ProductMatch{
		BrandID: product.BrandID, ProductID: product.ProductID,
		BrandNameSnapshot: product.BrandName, ProductLabelSnapshot: product.ProductLabel,
		BrandUpdatedAt: product.BrandUpdatedAt, ProductUpdatedAt: product.ProductUpdatedAt,
		ProductFitScore: total, ProductFitBand: domain.ProductFitBandFromScore(total),
		Excluded: excludeReason != "" && excluded, ExcludeReason: excludeReason,
		Reasons: reasons, Risks: risks, MatchedTerms: matchedTerms, MatchedAt: domain.NowNano(),
	}
	if err := match.ValidateForWrite(); err != nil {
		return nil, err
	}
	return match, nil
}

// Method form is convenient for sweep/pipeline callers and keeps the scorer
// independent of repository state.
func (s *Service) ScoreProductFit(cand *domain.CandidatePost, product *ProductContextSnapshot) (*domain.ProductMatch, error) {
	return ScoreProductFit(cand, product)
}

func firstProductEvidence(corpus string, terms []string, product *ProductContextSnapshot) (basis, excerpt string) {
	for _, raw := range terms {
		term := strings.TrimSpace(raw)
		if term == "" || productNameOnly(term, product) {
			continue
		}
		if found := findProductExcerpt(corpus, term); found != "" {
			return term, found
		}
	}
	return "", ""
}

func productNameOnly(term string, product *ProductContextSnapshot) bool {
	term = strings.TrimSpace(term)
	if term == "" {
		return true
	}
	return strings.EqualFold(term, strings.TrimSpace(product.ProductLabel)) || strings.EqualFold(term, strings.TrimSpace(product.BrandName))
}

func findProductExcerpt(corpus, term string) string {
	corpus = strings.TrimSpace(corpus)
	term = strings.TrimSpace(term)
	if corpus == "" || term == "" {
		return ""
	}
	lowerCorpus, lowerTerm := strings.ToLower(corpus), strings.ToLower(term)
	idx := strings.Index(lowerCorpus, lowerTerm)
	if idx < 0 {
		return ""
	}
	return strings.TrimSpace(corpus[idx : idx+len(term)])
}

func productDimensionLabel(dimension string) string {
	switch dimension {
	case domain.ProductFitPain:
		return "痛點"
	case domain.ProductFitScenario:
		return "情境／標籤"
	case domain.ProductFitAudience:
		return "目標受眾"
	case domain.ProductFitCapability:
		return "能力"
	default:
		return "上下文"
	}
}

func productCandidateExclusion(corpus string, cand *domain.CandidatePost, product *ProductContextSnapshot) (bool, string) {
	for _, raw := range product.ProviderExcludeTerms {
		term := strings.TrimSpace(raw)
		if term != "" && findProductExcerpt(corpus, term) != "" {
			return true, fmt.Sprintf("貼文命中產品排除詞「%s」", term)
		}
	}
	classification := strings.ToLower(strings.TrimSpace(cand.Classification))
	switch classification {
	case "provider_offer", "announcement", "noise":
		return true, "貼文分類為" + classification + "，不是產品需求"
	}
	// Catch only explicit provider/announcement markers; generic words such as
	// "推薦" are intentionally not hard exclusions.
	for _, marker := range []string{"歡迎洽詢", "接案中", "品牌公告"} {
		if containsProductMarker(corpus, marker) {
			return true, "貼文呈現同業供給或品牌公告語氣"
		}
	}
	return false, ""
}

func containsProductMarker(corpus, marker string) bool {
	return strings.Contains(strings.ToLower(corpus), strings.ToLower(marker))
}
