package usecase

import (
	"context"
	"strings"
	"testing"

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

func TestPrepareActivityBriefUsesGeneratedTerms(t *testing.T) {
	svc := &Service{}
	brief, err := svc.PrepareBrief(context.Background(), 1, "市集", "", "", "activity", true)
	if err != nil {
		t.Fatalf("PrepareBrief: %v", err)
	}
	if brief.Mode != domain.ModeActivity {
		t.Fatalf("mode=%q want %q", brief.Mode, domain.ModeActivity)
	}
	if len(brief.ScanTerms) < 2 {
		t.Fatalf("ScanTerms=%v want generated variants", brief.ScanTerms)
	}
}

func TestPlanActivityTermsGeneratesVariantsNotJustSplit(t *testing.T) {
	// 單一字主題：應產出「核 + 口語錨點」，不是只有原字
	got := planActivityTerms("市集")
	if len(got) < 3 {
		t.Fatalf("市集 variants=%v want >=3 generated terms", got)
	}
	hasPair := false
	for _, term := range got {
		if !radarDomain.IsThreadsSearchable(term) {
			t.Fatalf("term %q not Threads-searchable", term)
		}
		if strings.Contains(term, " ") {
			hasPair = true
		}
	}
	if !hasPair {
		t.Fatalf("expected multi-token variants, got %v", got)
	}

	// 長句：不應只回整句，要抽出核並產變體
	got2 := planActivityTerms("想找台北週末市集可以拍什麼")
	if len(got2) < 2 {
		t.Fatalf("long intent variants=%v want >=2", got2)
	}
	for _, term := range got2 {
		if !radarDomain.IsThreadsSearchable(term) {
			t.Fatalf("long-intent term %q not searchable", term)
		}
		// 不應留下整句長字串
		if utf8Count(term) > 12 {
			t.Fatalf("term too long: %q", term)
		}
	}
	// 應含地區或市集相關
	joined := strings.Join(got2, "|")
	if !strings.Contains(joined, "市集") && !strings.Contains(joined, "台北") {
		t.Fatalf("expected 市集/台北 in variants, got %v", got2)
	}
}

func TestPlanActivityTermsNotJustTokenizeIntent(t *testing.T) {
	// 舊行為：tokenizeIntent("週末市集") → ["週末市集"] 或空拆
	// 新行為：至少有錨點變體
	got := planActivityTerms("週末市集")
	if len(got) < 2 {
		t.Fatalf("got %v, want multiple generated variants", got)
	}
	// 若只有把四字當一詞，也要再有「週末 市集」類或「市集 推薦」
	onlyExact := len(got) == 1 && got[0] == "週末市集"
	if onlyExact {
		t.Fatal("still only returning exact intent — not generating")
	}
}

func TestPlanActivityTermsInfersBackendFreelanceIntent(t *testing.T) {
	got := planActivityTerms("外包 工程師 後端")
	if len(got) == 0 || got[0] != "後端 外包" {
		t.Fatalf("terms=%v want 後端 外包 as primary intent query", got)
	}
	joined := "|" + strings.Join(got, "|") + "|"
	for _, want := range []string{"|後端 外包|", "|後端 接案|", "|後端 工程師|"} {
		if !strings.Contains(joined, want) {
			t.Fatalf("terms=%v missing inferred query %s", got, want)
		}
	}
	for _, term := range got {
		if !radarDomain.IsThreadsSearchable(term) {
			t.Fatalf("term %q not Threads-searchable", term)
		}
	}
}

func TestPlanActivityTermsPreservesExactTitleIntent(t *testing.T) {
	got := planActivityTerms("鬼滅之刃")
	if len(got) == 0 || got[0] != "鬼滅之刃" {
		t.Fatalf("terms=%v want exact title as primary query", got)
	}
	joined := "|" + strings.Join(got, "|") + "|"
	for _, want := range []string{"|鬼滅之刃 最新|", "|鬼滅之刃 熱門|"} {
		if !strings.Contains(joined, want) {
			t.Fatalf("terms=%v missing discovery query %s", got, want)
		}
	}
	for _, bad := range []string{"鬼滅之", "滅之刃"} {
		if strings.Contains(joined, "|"+bad+"|") {
			t.Fatalf("broken title fragment %q leaked: %v", bad, got)
		}
	}
}

func TestMergeActivityTermsKeepsDeterministicIntentFirst(t *testing.T) {
	ai := []string{"市集 分享", "文青 市集", "太長了吧這整句不行"}
	rules := []string{"市集 推薦", "市集"}
	got := mergeActivityTerms(ai, rules, 6)
	if len(got) == 0 {
		t.Fatal("empty merge")
	}
	if got[0] != "市集 推薦" {
		t.Fatalf("deterministic intent term should come first, got %v", got)
	}
	// 長詞應被濾掉
	for _, t2 := range got {
		if strings.Contains(t2, "太長") {
			t.Fatalf("unssearchable term leaked: %v", got)
		}
	}
}

func TestParseActivityAITerms(t *testing.T) {
	raw := "這裡是開場\n[\"市集 分享\", \"週末 市集\"]\n結尾"
	got := parseActivityAITerms(raw)
	if len(got) != 2 {
		t.Fatalf("parse strings: %v", got)
	}
	raw2 := `[{"term":"市集 心得","reason":"x"},{"term":"活動 市集"}]`
	got2 := parseActivityAITerms(raw2)
	if len(got2) != 2 {
		t.Fatalf("parse objects: %v", got2)
	}
}

func utf8Count(s string) int {
	return len([]rune(strings.ReplaceAll(s, " ", "")))
}
