package knowledge

import (
	"context"
	"fmt"
	"sync"
	"testing"

	"haixun-backend/internal/library/websearch"
)

func TestUniqueSourceCount(t *testing.T) {
	count := uniqueSourceCount([]BraveSource{
		{URL: "https://a.example"},
		{URL: "https://a.example"},
		{URL: "https://b.example"},
		{URL: ""},
	})
	if count != 2 {
		t.Fatalf("expected 2 unique urls, got %d", count)
	}
}

func TestSourcesAreSufficientRequiresProcessedQuality(t *testing.T) {
	items := make([]BraveSource, 0, 14)
	for i := 0; i < 14; i++ {
		items = append(items, BraveSource{
			Query: "q1",
			URL:   fmt.Sprintf("https://example.com/%d", i),
			Title: "薄",
		})
	}
	cfg := BraveCollectConfig{MinSourcesBeforeStop: 14, MaxSourcesCap: 22}
	if sourcesAreSufficient(items, []string{"q1", "q2"}, cfg) {
		t.Fatal("thin same-query results should not be considered sufficient")
	}

	for i := range items {
		items[i].Snippet = "這是一段整理後足以支撐判斷的搜尋摘要內容，包含痛點與情境"
		if i >= 7 {
			items[i].Query = "q2"
		}
	}
	if !sourcesAreSufficient(items, []string{"q1", "q2"}, cfg) {
		t.Fatal("diverse useful results should be considered sufficient")
	}
}

func TestCollectWebSourcesParallelStopsAfterSufficientBatch(t *testing.T) {
	client := &fakeSearchClient{results: map[string][]websearch.SearchResult{}}
	for qi := 0; qi < 5; qi++ {
		query := fmt.Sprintf("q%d", qi+1)
		for ri := 0; ri < 7; ri++ {
			client.results[query] = append(client.results[query], websearch.SearchResult{
				URL:     fmt.Sprintf("https://example.com/%s/%d", query, ri),
				Title:   "有用來源",
				Snippet: "這是一段整理後足以支撐判斷的搜尋摘要內容，包含痛點與情境",
			})
		}
	}

	out := CollectWebSources(
		context.Background(),
		client,
		BraveSearchLocale{},
		[]string{"q1", "q2", "q3", "q4", "q5"},
		BraveCollectConfig{ResultsPerQuery: 7, MinSourcesBeforeStop: 14, MaxSourcesCap: 30, Concurrency: 2},
		nil,
		nil,
	)
	if len(out) != 14 {
		t.Fatalf("expected first batch to provide 14 sources, got %d", len(out))
	}
	if got := client.callCount(); got != 2 {
		t.Fatalf("expected collection to stop after first sufficient batch, called %d queries", got)
	}
}

func TestPlanQueriesHybridBudget(t *testing.T) {
	queries := PlanQueries(PlanInput{
		Seed:           "敏感肌",
		TargetAudience: "孕婦",
		Questions:      []string{"q1", "q2", "q3", "q4"},
		Pillars:        []string{"p1", "p2", "p3"},
		L1Labels:       []string{"a", "b", "c", "d"},
		Strategy:       ExpandStrategyHybrid,
	})
	if len(queries) > 4 {
		t.Fatalf("hybrid should cap at 4 queries, got %d: %v", len(queries), queries)
	}
	if len(queries) == 0 {
		t.Fatal("expected some queries")
	}
}

func TestPlanQueriesPrioritizesPatrolKeywords(t *testing.T) {
	queries := PlanQueries(PlanInput{
		Seed:           "敏感肌",
		PatrolKeywords: []string{"化療 沐浴乳", "無香沐浴乳 推薦"},
		Questions:      []string{"很長的受眾提問一", "很長的受眾提問二"},
		Strategy:       ExpandStrategyBrave,
	})
	if len(queries) == 0 {
		t.Fatal("expected queries")
	}
	if queries[0] != "化療 沐浴乳" {
		t.Fatalf("expected patrol keyword first, got %q", queries[0])
	}
}

func TestQueriesExcept(t *testing.T) {
	remaining := QueriesExcept(
		[]string{"a", "b", "c", "d"},
		[]string{"a", "c"},
	)
	if len(remaining) != 2 || remaining[0] != "b" || remaining[1] != "d" {
		t.Fatalf("unexpected remaining: %v", remaining)
	}
}

func TestPlanBootstrapQueriesSkipsResearchMapFields(t *testing.T) {
	full := PlanQueries(PlanInput{
		Seed:      "敏感肌",
		Questions: []string{"q1"},
		Pillars:   []string{"p1"},
		Strategy:  ExpandStrategyBrave,
	})
	bootstrap := PlanBootstrapQueries(PlanInput{
		Seed:      "敏感肌",
		Questions: []string{"q1"},
		Pillars:   []string{"p1"},
		Strategy:  ExpandStrategyBrave,
	})
	if len(bootstrap) == 0 {
		t.Fatal("expected bootstrap queries")
	}
	for _, q := range bootstrap {
		if q == "q1" || q == "p1 請問" {
			t.Fatalf("bootstrap should not include research map query %q", q)
		}
	}
	if len(full) <= len(bootstrap) {
		t.Fatalf("full plan should include more queries than bootstrap: full=%v bootstrap=%v", full, bootstrap)
	}
}

func TestMergeBraveSourcesDedupesURLs(t *testing.T) {
	merged := MergeBraveSources(
		[]BraveSource{{URL: "https://a.example", Query: "q1"}},
		[]BraveSource{{URL: "https://a.example", Query: "q2"}, {URL: "https://b.example", Query: "q3"}},
	)
	if len(merged) != 2 {
		t.Fatalf("expected 2 merged sources, got %d", len(merged))
	}
}

func TestSupplementalQueriesSkippedForHybrid(t *testing.T) {
	queries := PlanQueries(PlanInput{
		Seed:         "敏感肌",
		Supplemental: true,
		Strategy:     ExpandStrategyHybrid,
	})
	if len(queries) != 0 {
		t.Fatalf("hybrid supplemental brave should be empty, got %v", queries)
	}
}

type fakeSearchClient struct {
	mu      sync.Mutex
	calls   []string
	results map[string][]websearch.SearchResult
}

func (f *fakeSearchClient) Enabled() bool { return true }

func (f *fakeSearchClient) Provider() websearch.Provider { return websearch.ProviderBrave }

func (f *fakeSearchClient) Search(_ context.Context, opts websearch.SearchOptions) (websearch.SearchResponse, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.results == nil {
		f.results = map[string][]websearch.SearchResult{}
	}
	f.calls = append(f.calls, opts.Query)
	return websearch.SearchResponse{
		Query:    opts.Query,
		Status:   "success",
		Provider: websearch.ProviderBrave,
		Results:  append([]websearch.SearchResult(nil), f.results[opts.Query]...),
	}, nil
}

func (f *fakeSearchClient) callCount() int {
	f.mu.Lock()
	defer f.mu.Unlock()
	return len(f.calls)
}
