package usecase

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

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

type demandMapEnrichment struct {
	PainPhrases      []domain.DemandMapPhrase `json:"pain_phrases"`
	ScenarioPhrases  []domain.DemandMapPhrase `json:"scenario_phrases"`
	DesiredOutcomes  []domain.DemandMapPhrase `json:"desired_outcomes"`
	SolutionSignals  []domain.DemandMapPhrase `json:"solution_signals"`
	ExclusionSignals []domain.DemandMapPhrase `json:"exclusion_signals"`
	CustomPhrases    []domain.DemandMapPhrase `json:"custom_phrases"`
}

func (s *Service) EnrichDemandMap(ctx context.Context, ownerUID int64, productID, previewID string, expectedVersion int64, creditCeiling int) (out *domain.DemandMap, err error) {
	if ownerUID <= 0 || strings.TrimSpace(productID) == "" {
		return nil, fmt.Errorf("%w: owner and product are required", domain.ErrValidation)
	}
	if expectedVersion < 1 {
		return nil, fmt.Errorf("%w: expected map version required", domain.ErrValidation)
	}
	if creditCeiling < 1 {
		return nil, fmt.Errorf("%w: credit ceiling must be positive", domain.ErrValidation)
	}
	current, err := s.GetDemandMap(ctx, ownerUID, productID)
	if err != nil {
		return nil, err
	}
	charge, err := s.bill(ctx, ownerUID, usageDomain.MeterAICopy, "需求地圖 AI 補全", "radar.demand_map_enrich")
	if err != nil {
		return nil, err
	}
	defer charge.Settle(ctx, &err)
	if s.AI == nil && s.AIRegistry == nil && s.ResolveAI == nil {
		return nil, fmt.Errorf("%w: AI provider unavailable", domain.ErrNotReady)
	}
	prompt := fmt.Sprintf("請只輸出 JSON 物件，根據需求地圖補充使用者會說的短詞，不要使用品牌或產品名稱。痛點=%q；情境=%q；結果=%q；能力=%q。欄位為 pain_phrases、scenario_phrases、desired_outcomes、solution_signals、exclusion_signals、custom_phrases，每則含 text、kind、basis_kind、basis_text、origin=ai、enabled=true。include 類 text 必須是 Threads 可搜短詞：最多 2 個 token、中文每 token 2–4 字、去空格後 ≤12 字、禁止標點。", phraseTexts(current.PainPhrases), phraseTexts(current.ScenarioPhrases), phraseTexts(current.DesiredOutcomes), phraseTexts(current.SolutionSignals))
	raw, err := s.completeAI(ctx, ownerUID, prompt)
	if err != nil {
		return nil, err
	}
	var additions demandMapEnrichment
	start, end := strings.Index(raw, "{"), strings.LastIndex(raw, "}")
	if start < 0 || end <= start || json.Unmarshal([]byte(raw[start:end+1]), &additions) != nil {
		return nil, fmt.Errorf("%w: AI returned invalid demand map JSON", domain.ErrValidation)
	}
	mergePhrases := func(existing []domain.DemandMapPhrase, incoming []domain.DemandMapPhrase) []domain.DemandMapPhrase {
		out := append([]domain.DemandMapPhrase(nil), existing...)
		seen := map[string]bool{}
		for _, p := range out {
			seen[strings.ToLower(strings.TrimSpace(p.Text))] = true
		}
		for _, p := range incoming {
			p.Text = strings.TrimSpace(p.Text)
			p.Origin = "ai"
			p.Enabled = p.Enabled && p.Text != ""
			if !p.Enabled {
				continue
			}
			texts := []string{p.Text}
			if p.Kind != "exclusion" {
				texts = domain.SearchableTermVariants(p.Text, true)
				if len(texts) > domain.MaxSearchableVariantsPerTerm {
					texts = texts[:domain.MaxSearchableVariantsPerTerm]
				}
			}
			basis := strings.TrimSpace(p.BasisText)
			if basis == "" {
				basis = p.Text
			}
			for _, text := range texts {
				key := strings.ToLower(text)
				if text == "" || seen[key] {
					continue
				}
				cp := p
				cp.Text = text
				cp.BasisText = basis
				out = append(out, cp)
				seen[key] = true
			}
		}
		return out
	}
	current.PainPhrases = mergePhrases(current.PainPhrases, additions.PainPhrases)
	current.ScenarioPhrases = mergePhrases(current.ScenarioPhrases, additions.ScenarioPhrases)
	current.DesiredOutcomes = mergePhrases(current.DesiredOutcomes, additions.DesiredOutcomes)
	current.SolutionSignals = mergePhrases(current.SolutionSignals, additions.SolutionSignals)
	current.ExclusionSignals = mergePhrases(current.ExclusionSignals, additions.ExclusionSignals)
	current.CustomPhrases = mergePhrases(current.CustomPhrases, additions.CustomPhrases)
	current.AIEnrichedAt = domain.NowNano()
	if current.State != "ready" && len(current.PainPhrases) > 0 && len(current.ScenarioPhrases) > 0 && len(current.SolutionSignals) > 0 {
		current.State = "ready"
	}
	_ = previewID // preview is a client confirmation token; no credit is reserved by preview.
	return s.UpdateDemandMap(ctx, ownerUID, current, expectedVersion)
}

func phraseTexts(in []domain.DemandMapPhrase) []string {
	out := make([]string, 0, len(in))
	for _, p := range in {
		if p.Enabled && strings.TrimSpace(p.Text) != "" {
			out = append(out, strings.TrimSpace(p.Text))
		}
	}
	return out
}
