package usecase

import (
	"context"
	"errors"
	"strings"
	"testing"

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

func TestSuggestProductWatchTermsReturnsTraceableAIItems(t *testing.T) {
	svc := New(nil)
	svc.ProductSource = productSourceFixture{
		brand:   &ProductBrand{ID: "b1", OwnerUID: 42, DisplayName: "清透品牌", TargetAudience: "大人"},
		product: &ProductCatalogProduct{ID: "p1", BrandID: "b1", OwnerUID: 42, Label: "溫和保養", ProductContext: "換季容易不舒服", PainPoints: []string{"敏感肌"}, MatchTags: []string{"求推薦"}},
	}
	svc.AI = &stubAI{reply: `[{"term":"敏感肌","reason":"可找到正在處理敏感肌困擾的人","usage":"include","basis_kind":"pain","basis_text":"敏感肌"}]`}

	list, err := svc.SuggestProductWatchTerms(context.Background(), 42, "b1", "p1", 8)
	if err != nil {
		t.Fatalf("suggest: %v", err)
	}
	if len(list) != 1 {
		t.Fatalf("got %d suggestions, want one", len(list))
	}
	item := list[0]
	if item.BasisKind != domain.SuggestBasisPain || item.BasisText != "敏感肌" {
		t.Fatalf("basis not preserved: %+v", item)
	}
	if !domain.IsThreadsSearchable(item.Term) || strings.TrimSpace(item.Reason) == "" {
		t.Fatalf("invalid suggestion: %+v", item)
	}
}

func TestSuggestProductWatchTermsFallsBackToCatalogWithBasis(t *testing.T) {
	svc := New(nil)
	svc.ProductSource = productSourceFixture{
		brand: &ProductBrand{ID: "b1", OwnerUID: 42, DisplayName: "清透品牌", TargetAudience: "大人"},
		product: &ProductCatalogProduct{
			ID: "p1", BrandID: "b1", OwnerUID: 42, Label: "溫和保養",
			ProductContext: "保養", PainPoints: []string{"敏感肌"}, MatchTags: []string{"求推薦"},
			ProviderCapabilityTerms: []string{"溫和保養"}, ProviderExcludeTerms: []string{"徵才"},
		},
	}
	// No AI client is configured: this must still return a non-empty, auditable fallback.
	list, err := svc.SuggestProductWatchTerms(context.Background(), 42, "b1", "p1", 20)
	if err != nil {
		t.Fatalf("fallback suggest: %v", err)
	}
	if len(list) == 0 {
		t.Fatal("fallback returned no suggestions")
	}
	hasExclude := false
	for _, item := range list {
		if strings.TrimSpace(item.BasisKind) == "" || strings.TrimSpace(item.BasisText) == "" || strings.TrimSpace(item.Reason) == "" {
			t.Fatalf("untraceable suggestion: %+v", item)
		}
		if item.Usage == domain.SuggestUsageInclude {
			if !domain.IsThreadsSearchable(item.Term) {
				t.Fatalf("include violates short-term rule: %+v", item)
			}
			if item.BasisKind == domain.SuggestBasisExclude {
				t.Fatalf("include has exclude basis: %+v", item)
			}
		} else if item.Usage == domain.SuggestUsageExclude {
			hasExclude = true
			if item.BasisKind != domain.SuggestBasisExclude {
				t.Fatalf("exclude basis mismatch: %+v", item)
			}
		}
	}
	if !hasExclude {
		t.Fatal("fallback did not retain provider exclude term")
	}
}

func TestSuggestProductWatchTermsRequiresPairedOwnedContext(t *testing.T) {
	svc := New(nil)
	svc.ProductSource = productSourceFixture{
		brand:   &ProductBrand{ID: "b1", OwnerUID: 42},
		product: &ProductCatalogProduct{ID: "p1", BrandID: "b1", OwnerUID: 42, PainPoints: []string{"敏感肌"}},
	}
	if _, err := svc.SuggestProductWatchTerms(context.Background(), 42, "b1", "", 8); !errors.Is(err, domain.ErrValidation) {
		t.Fatalf("unpaired IDs err=%v, want validation", err)
	}
	if _, err := svc.SuggestProductWatchTerms(context.Background(), 42, "b1", "other", 8); !errors.Is(err, domain.ErrNotFound) {
		t.Fatalf("missing product err=%v, want not found", err)
	}
	if _, err := svc.SuggestProductWatchTerms(context.Background(), 42, "b1", "p1", 8); err != nil {
		t.Fatalf("valid paired IDs: %v", err)
	}
}
