package usecase

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

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

const validProductFitReply = `{
  "product_fit_score": 100,
  "product_fit_band": "strong",
  "excluded": false,
  "reasons": [
    {"dimension":"pain","score":35,"reason":"貼文描述相同困擾","candidate_excerpt":"泛紅不適","product_basis":"泛紅不適"},
    {"dimension":"scenario","score":25,"reason":"貼文是換季保養情境","candidate_excerpt":"換季保養","product_basis":"換季保養"},
    {"dimension":"audience","score":20,"reason":"貼文明確提到受眾","candidate_excerpt":"敏感肌","product_basis":"敏感肌"},
    {"dimension":"capability","score":20,"reason":"需求可由能力處理","candidate_excerpt":"舒緩修護","product_basis":"舒緩修護"}
  ],
  "risks": []
}`

func productJudgeFixture(reply string) (*Service, *domain.RadarWatch, *domain.CandidatePost) {
	svc := New(nil)
	svc.ProductSource = productSourceFixture{brand: &ProductBrand{ID: "b1", OwnerUID: 42, DisplayName: "清透品牌", TargetAudience: "敏感肌"}, product: &ProductCatalogProduct{ID: "p1", BrandID: "b1", OwnerUID: 42, Label: "溫和保養", PainPoints: []string{"泛紅不適"}, MatchTags: []string{"換季保養"}, ProductContext: "換季保養", ProviderCapabilityTerms: []string{"舒緩修護"}}}
	svc.AI = &stubAI{reply: reply}
	return svc, &domain.RadarWatch{ID: "w1", OwnerUID: 42, Status: domain.WatchActive, ContextMode: domain.WatchContextProduct, BrandID: "b1", ProductID: "p1"}, &domain.CandidatePost{ExternalID: "e1", Text: "敏感肌換季保養泛紅不適，想找舒緩修護", Classification: "demand"}
}

func TestJudgeProductCandidateAcceptsOnlyStructuredEvidence(t *testing.T) {
	svc, watch, cand := productJudgeFixture(validProductFitReply)
	result, _, err := svc.JudgeCandidate(context.Background(), 42, nil, watch, cand)
	if err != nil {
		t.Fatal(err)
	}
	if result.ProductMatch == nil || result.ProductMatch.ProductFitScore != 100 || !result.ProductMatch.Eligible {
		t.Fatalf("product fit not attached: %+v", result)
	}
	for _, reason := range result.ProductMatch.Reasons {
		if reason.Score > 0 && !strings.Contains(cand.Text, reason.CandidateExcerpt) {
			t.Fatalf("invented excerpt: %+v", reason)
		}
	}
}

func TestJudgeProductCandidateInvalidAIExcerptFallsBack(t *testing.T) {
	bad := strings.Replace(validProductFitReply, `"candidate_excerpt":"泛紅不適"`, `"candidate_excerpt":"模型腦補的症狀"`, 1)
	svc, watch, cand := productJudgeFixture(bad)
	result, _, err := svc.JudgeCandidate(context.Background(), 42, nil, watch, cand)
	if err != nil {
		t.Fatal(err)
	}
	if result.ProductMatch == nil || result.ProductMatch.ProductFitScore != 100 {
		t.Fatalf("deterministic fallback did not score source evidence: %+v", result.ProductMatch)
	}
	for _, reason := range result.ProductMatch.Reasons {
		if reason.Score > 0 && !strings.Contains(cand.Text, reason.CandidateExcerpt) {
			t.Fatalf("fallback invented excerpt: %+v", reason)
		}
	}
}

func TestJudgeProductCandidateProviderFailureStillReturnsFourReasons(t *testing.T) {
	svc, watch, cand := productJudgeFixture("")
	svc.AI.(*stubAI).err = errors.New("provider down")
	result, _, err := svc.JudgeCandidate(context.Background(), 42, nil, watch, cand)
	if err != nil {
		t.Fatal(err)
	}
	if result.ProductMatch == nil || len(result.ProductMatch.Reasons) != 4 {
		t.Fatalf("provider failure returned empty product reasons: %+v", result)
	}
}
