package repository

import (
	"context"
	"sync"
	"testing"

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

func validProductMatch(id string, score int) *domain.ProductMatch {
	m := &domain.ProductMatch{BrandID: "b1", ProductID: id, ProductFitScore: score, Reasons: []domain.ProductFitReason{
		{Dimension: domain.ProductFitPain, Score: min(score, 35), Reason: "pain", CandidateExcerpt: "漏水", ProductBasis: "漏水"},
		{Dimension: domain.ProductFitScenario, Score: min(max(score-35, 0), 25), Reason: "scenario", CandidateExcerpt: "找人", ProductBasis: "居家"},
		{Dimension: domain.ProductFitAudience, Score: min(max(score-60, 0), 20), Reason: "audience", CandidateExcerpt: "家裡", ProductBasis: "家庭"},
		{Dimension: domain.ProductFitCapability, Score: min(max(score-80, 0), 20), Reason: "capability", CandidateExcerpt: "抓漏", ProductBasis: "抓漏"},
	}}
	return m
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func TestMemoryProductMatchMergeIsAtomicAndKeepsSnapshot(t *testing.T) {
	ctx := context.Background()
	m := NewMemory()
	o := sampleOpp("product-ext", []string{"找抓漏"}, 80)
	created, err := m.UpsertByExternalID(ctx, o)
	if err != nil {
		t.Fatal(err)
	}
	base := validProductMatch("p1", 80)
	base.ProductLabelSnapshot = "舊名稱"
	base.WatchIDs = []string{"w1"}
	base.MatchedTerms = []string{"漏水"}
	if _, err := m.MergeProductMatch(ctx, 42, created.ID, base); err != nil {
		t.Fatal(err)
	}
	var wg sync.WaitGroup
	for i := 0; i < 8; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			next := validProductMatch("p1", 20)
			next.ProductLabelSnapshot = "新名稱"
			next.WatchIDs = []string{"w2"}
			_, _ = m.MergeProductMatch(ctx, 42, created.ID, next)
		}(i)
	}
	wg.Wait()
	got, err := m.GetOpportunity(ctx, created.ID)
	if err != nil {
		t.Fatal(err)
	}
	if len(got.ProductMatches) != 1 || got.ProductMatches[0].ProductLabelSnapshot != "舊名稱" {
		t.Fatalf("duplicate or snapshot rewrite: %#v", got.ProductMatches)
	}
	if len(got.ProductMatches[0].WatchIDs) != 2 {
		t.Fatalf("watch sources not merged: %#v", got.ProductMatches[0].WatchIDs)
	}
}

func TestMemoryPrimaryOverrideBlocksAutoArbitration(t *testing.T) {
	ctx := context.Background()
	m := NewMemory()
	created, err := m.UpsertByExternalID(ctx, sampleOpp("primary-ext", []string{"x"}, 80))
	if err != nil {
		t.Fatal(err)
	}
	low := validProductMatch("p1", 60)
	high := validProductMatch("p2", 90)
	if _, err := m.MergeProductMatch(ctx, 42, created.ID, low); err != nil {
		t.Fatal(err)
	}
	if _, err := m.SetPrimaryProduct(ctx, 42, created.ID, "p1", &domain.ProductPrimaryOverride{ActorUID: 42, Reason: "人工指定"}); err != nil {
		t.Fatal(err)
	}
	got, err := m.MergeProductMatch(ctx, 42, created.ID, high)
	if err != nil {
		t.Fatal(err)
	}
	if got.PrimaryProductID != "p1" || !got.PrimaryProductOverridden {
		t.Fatalf("override overwritten: %#v", got)
	}
}
