package usecase_test

import (
	"context"
	"testing"

	"apps/backend/internal/module/scout/domain"
	"apps/backend/internal/module/scout/repository"
	"apps/backend/internal/module/scout/usecase"

	"github.com/stretchr/testify/require"
)

type capturedSearchProvider struct {
	// queries records each fan-out call (one term per call)
	queries []string
	hits    []usecase.ThreadSearchResult
}

func TestPrepareBriefPlansDeduplicatedTermsFromAllInputs(t *testing.T) {
	svc := usecase.New(repository.NewMemory())
	brand, err := svc.CreateBrand(context.Background(), 1, "B", "")
	require.NoError(t, err)
	product, err := svc.SaveProduct(context.Background(), 1, &domain.Product{
		BrandID: brand.ID, Label: "P", PainPoints: []string{"敏感肌", "敏感肌"}, MatchTags: []string{"保養", "敏感肌"},
	})
	require.NoError(t, err)
	brief, err := svc.PrepareBrief(context.Background(), 1, "敏感肌保養", brand.ID, product.ID, "value", false)
	require.NoError(t, err)
	// 每條是獨立 query；含 intent／痛點／標籤與求助 variant；不含 periphery
	require.Equal(t, []string{"敏感肌保養", "敏感肌", "保養", "敏感肌 求推薦"}, brief.ScanTerms)
	require.NotContains(t, brief.ScanTerms, "使用情境")
}

func (p *capturedSearchProvider) SearchThreads(_ context.Context, terms []string, _ int) ([]usecase.ThreadSearchResult, error) {
	if len(terms) > 0 {
		p.queries = append(p.queries, terms[0])
	}
	return p.hits, nil
}

func TestPlannerProductSignalsAndDeterministicPersistence(t *testing.T) {
	provider := &capturedSearchProvider{hits: []usecase.ThreadSearchResult{
		{URL: "https://www.threads.net/@a/post/1?utm_source=test", Snippet: "請問敏感肌怎麼舒緩？", PublishedAt: 300},
		{URL: "https://www.threads.net/@b/post/2", Snippet: "敏感肌保養有沒有推薦？", PublishedAt: 200},
		{URL: "https://www.threads.net/@c/post/3", Snippet: "敏感肌服務洽詢，現在限時優惠", PublishedAt: 100},
		{URL: "https://www.threads.net/@d/post/4", Snippet: "分享敏感肌保養的使用心得", PublishedAt: 50},
		{URL: "https://www.threads.net/@e/post/5", Snippet: "敏感肌抽獎，互追拿好禮", PublishedAt: 10},
	}}
	store := repository.NewMemory()
	svc := usecase.New(store)
	svc.Provider = provider
	brief := &domain.RunBrief{
		Mode: domain.ModeProduct, Intent: "敏感肌保養", Pains: []string{"敏感肌", "敏感肌"},
		Tags: []string{"保養", "敏感肌"}, Periphery: []string{"使用情境", "保養"},
	}
	brief.ScanTerms = []string{"敏感肌", "保養", "使用情境", "敏感肌保養"}

	posts, err := svc.RunScanFromBrief(context.Background(), 42, brief)
	require.NoError(t, err)
	// 多 query 扇出：每條 scan_term 各搜一次
	require.Equal(t, []string{"敏感肌", "保養", "使用情境", "敏感肌保養"}, provider.queries)
	// product 模式略過 provider_offer（硬廣），noise 也略過 → 剩 3
	require.Len(t, posts, 3)
	// score 主排序；發文時間只在同分時決勝
	require.Equal(t, "https://www.threads.net/@b/post/2", posts[0].Permalink)
	require.Equal(t, int64(200), posts[0].PostedAt)
	byClass := map[string]*domain.Post{}
	for _, post := range posts {
		byClass[post.Classification] = post
		require.NotEmpty(t, post.Permalink)
		require.Equal(t, post.Permalink, post.ExternalID)
		require.InDelta(t, 0, post.Score, 100)
		require.NotEmpty(t, post.MatchReason)
		require.NotEqual(t, domain.ClassificationProviderOffer, post.Classification)
	}
	require.Contains(t, byClass, domain.ClassificationSeekingHelp)
	require.Contains(t, byClass, domain.ClassificationSeekingRecommendation)
	require.Contains(t, byClass, domain.ClassificationDiscussion)
	require.Contains(t, byClass[domain.ClassificationSeekingHelp].MatchReason, "敏感肌")
	require.Equal(t, "https://www.threads.net/@a/post/1", byClass[domain.ClassificationSeekingHelp].Permalink)

	again, err := svc.RunScanFromBrief(context.Background(), 42, brief)
	require.NoError(t, err)
	require.Equal(t, byClass[domain.ClassificationSeekingRecommendation].ID, again[0].ID)
	persisted, err := svc.ListPosts(context.Background(), 42, "")
	require.NoError(t, err)
	require.Len(t, persisted, 3)
}

func TestPlannerActivityClassifiesWithNeutralRecency(t *testing.T) {
	provider := &capturedSearchProvider{hits: []usecase.ThreadSearchResult{
		{URL: "https://www.threads.net/@a/post/1", Snippet: "請問這週市集怎麼報名？"},
		{URL: "https://www.threads.net/@b/post/2", Snippet: "市集活動公告，週六開幕"},
		{URL: "https://www.threads.net/@c/post/3", Snippet: "市集攤位服務洽詢"},
		{URL: "https://www.threads.net/@d/post/4", Snippet: "分享市集逛街心得"},
		{URL: "https://www.threads.net/@e/post/5", Snippet: "市集 crypto 賺錢群組"},
	}}
	svc := usecase.New(repository.NewMemory())
	svc.Provider = provider
	brief := &domain.RunBrief{Mode: domain.ModeActivity, Intent: "市集", ScanTerms: []string{"市集"}}

	posts, err := svc.RunScanFromBrief(context.Background(), 7, brief)
	require.NoError(t, err)
	require.Len(t, posts, 4)
	classes := map[string]bool{}
	for _, post := range posts {
		classes[post.Classification] = true
		require.Contains(t, post.MatchReason, "recency unavailable (neutral)")
		require.Greater(t, post.Score, 0)
	}
	require.True(t, classes[domain.ClassificationAsking])
	require.True(t, classes[domain.ClassificationAnnouncement])
	require.True(t, classes[domain.ClassificationProviderOffer])
	require.True(t, classes[domain.ClassificationDiscussion])
}

func TestProviderScoutFindsProvenSolversAndExcludesSameCategory(t *testing.T) {
	provider := &capturedSearchProvider{hits: []usecase.ThreadSearchResult{
		{URL: "https://www.threads.net/@doctor/post/1", Snippet: "刺鼻頭皮問題可由皮膚科專業諮詢協助", PublishedAt: 300},
		{URL: "https://www.threads.net/@friend/post/2", Snippet: "推薦皮膚科處理刺鼻頭皮問題，有完整案例", PublishedAt: 200},
		{URL: "https://www.threads.net/@seller/post/3", Snippet: "刺鼻頭皮洗髮精，皮膚科配方限時優惠", PublishedAt: 100},
		{URL: "https://www.threads.net/@chat/post/4", Snippet: "刺鼻頭皮真的很困擾", PublishedAt: 50},
	}}
	svc := usecase.New(repository.NewMemory())
	svc.Provider = provider
	brand, err := svc.CreateBrand(context.Background(), 11, "B", "")
	require.NoError(t, err)
	p, err := svc.SaveProduct(context.Background(), 11, &domain.Product{
		BrandID: brand.ID, Label: "無香洗髮", PainPoints: []string{"刺鼻頭皮"},
		ProviderCapabilityTerms: []string{"皮膚科"}, ProviderExcludeTerms: []string{"洗髮精"},
	})
	require.NoError(t, err)
	brief, err := svc.PrepareBrief(context.Background(), 11, "", brand.ID, p.ID, "provider", false)
	require.NoError(t, err)
	require.Equal(t, domain.ModeProvider, brief.Mode)
	require.NotContains(t, brief.ScanTerms, p.Label)

	posts, err := svc.RunScanFromBrief(context.Background(), 11, brief)
	require.NoError(t, err)
	require.Len(t, posts, 2)
	require.Equal(t, domain.ClassificationProviderRecommended, posts[0].Classification)
	require.Equal(t, domain.ClassificationProviderDirect, posts[1].Classification)
}

func TestDemandScoutKeepsHelpSignalsWithoutProviderTerms(t *testing.T) {
	provider := &capturedSearchProvider{hits: []usecase.ThreadSearchResult{
		{URL: "https://www.threads.net/@need/post/1", Snippet: "頭皮一直發癢怎麼辦？"},
		{URL: "https://www.threads.net/@need/post/2", Snippet: "敏感頭皮洗髮精有沒有推薦"},
		{URL: "https://www.threads.net/@seller/post/3", Snippet: "頭皮發癢洗髮精限時優惠"},
		{URL: "https://www.threads.net/@chat/post/4", Snippet: "頭皮發癢的季節又到了"},
	}}
	svc := usecase.New(repository.NewMemory())
	svc.Provider = provider
	brand, err := svc.CreateBrand(context.Background(), 15, "B", "")
	require.NoError(t, err)
	p, err := svc.SaveProduct(context.Background(), 15, &domain.Product{
		BrandID: brand.ID, Label: "舒緩洗髮", PainPoints: []string{"頭皮發癢", "敏感頭皮"},
	})
	require.NoError(t, err)

	brief, err := svc.PrepareBrief(context.Background(), 15, "", brand.ID, p.ID, "demand", false)
	require.NoError(t, err)
	require.Equal(t, domain.ModeDemand, brief.Mode)
	require.Contains(t, brief.ScanTerms, "頭皮發癢 怎麼辦")

	posts, err := svc.RunScanFromBrief(context.Background(), 15, brief)
	require.NoError(t, err)
	require.Len(t, posts, 2)
	require.Equal(t, domain.ClassificationSeekingRecommendation, posts[0].Classification)
	require.Equal(t, domain.ClassificationSeekingHelp, posts[1].Classification)
}

func TestProviderScoutPlansShortHighIntentQueries(t *testing.T) {
	svc := usecase.New(repository.NewMemory())
	brand, err := svc.CreateBrand(context.Background(), 14, "B", "")
	require.NoError(t, err)
	p, err := svc.SaveProduct(context.Background(), 14, &domain.Product{
		BrandID: brand.ID,
		Label:   "無香洗髮",
		PainPoints: []string{
			"最近頭皮又乾又癢不知道怎麼辦",
		},
		ProviderCapabilityTerms: []string{
			"皮膚科敏感頭皮專業諮詢服務",
		},
	})
	require.NoError(t, err)

	brief, err := svc.PrepareBrief(context.Background(), 14, "", brand.ID, p.ID, "provider", false)
	require.NoError(t, err)
	require.NotEmpty(t, brief.ScanTerms)
	for _, term := range brief.ScanTerms {
		require.LessOrEqual(t, len([]rune(term)), 24)
		require.NotContains(t, term, "不知道怎麼辦")
	}
	require.Contains(t, brief.ScanTerms, "最近頭皮又乾又癢 推薦")
	require.Contains(t, brief.ScanTerms, "皮膚科敏感頭皮專業諮詢服務 推薦")
}

func TestProviderScoutUsesExistingMatchTagsWhenProviderFieldsAreEmpty(t *testing.T) {
	provider := &capturedSearchProvider{hits: []usecase.ThreadSearchResult{
		{URL: "https://www.threads.net/@doctor/post/1", Snippet: "頭皮刺鼻問題可由皮膚科專業諮詢協助"},
		{URL: "https://www.threads.net/@seller/post/2", Snippet: "頭皮刺鼻問題，皮膚科配方限時優惠"},
	}}
	svc := usecase.New(repository.NewMemory())
	svc.Provider = provider
	brand, err := svc.CreateBrand(context.Background(), 12, "B", "")
	require.NoError(t, err)
	p, err := svc.SaveProduct(context.Background(), 12, &domain.Product{
		BrandID: brand.ID, Label: "無香洗髮", PainPoints: []string{"頭皮刺鼻"}, MatchTags: []string{"皮膚科"},
	})
	require.NoError(t, err)

	brief, err := svc.PrepareBrief(context.Background(), 12, "", brand.ID, p.ID, "provider", false)
	require.NoError(t, err)
	require.Equal(t, []string{"皮膚科"}, brief.Tags)
	require.Contains(t, brief.Periphery, p.Label)

	posts, err := svc.RunScanFromBrief(context.Background(), 12, brief)
	require.NoError(t, err)
	require.Len(t, posts, 1)
	require.Equal(t, domain.ClassificationProviderDirect, posts[0].Classification)
}

func TestProviderScoutFallsBackToProductLabel(t *testing.T) {
	svc := usecase.New(repository.NewMemory())
	svc.Provider = &capturedSearchProvider{hits: []usecase.ThreadSearchResult{{
		URL: "https://www.threads.net/@expert/post/1", Snippet: "無香洗髮專業諮詢協助",
	}}}
	brand, err := svc.CreateBrand(context.Background(), 13, "B", "")
	require.NoError(t, err)
	p, err := svc.SaveProduct(context.Background(), 13, &domain.Product{BrandID: brand.ID, Label: "無香洗髮"})
	require.NoError(t, err)

	brief, err := svc.PrepareBrief(context.Background(), 13, "", brand.ID, p.ID, "provider", false)
	require.NoError(t, err)
	require.NotEmpty(t, brief.ScanTerms)
	posts, err := svc.RunScanFromBrief(context.Background(), 13, brief)
	require.NoError(t, err)
	require.Len(t, posts, 1)
}

func TestDraftOutreachIsDeterministicAndDoesNotNeedAI(t *testing.T) {
	svc := usecase.New(repository.NewMemory())
	svc.Provider = &capturedSearchProvider{hits: []usecase.ThreadSearchResult{{
		URL: "https://www.threads.net/@author/post/1", Snippet: "請問怎麼處理頭皮困擾？",
	}}}
	posts, err := svc.RunScanFromBrief(context.Background(), 9, &domain.RunBrief{
		Mode: domain.ModeTheme, Intent: "頭皮", ScanTerms: []string{"頭皮"},
	})
	require.NoError(t, err)
	draft, err := svc.DraftOutreach(context.Background(), 9, posts[0].ID, "")
	require.NoError(t, err)
	require.Equal(t, "嗨 @author，看到你提到「頭皮」，我也遇過類似情況。若你願意，想聽聽你後來怎麼處理。", draft.DraftText)
}
