package usecase

import (
	"context"
	"errors"
	"testing"
	"time"

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

func TestProductSweepWorksWithoutServiceProfileAndPersistsMatch(t *testing.T) {
	svc, source := productWatchService()
	svc.HitFetch = HitFetcherFunc(func(context.Context, int64, []string, int) ([]ThreadHit, string, error) {
		return []ThreadHit{{URL: "https://threads.net/post/product-1", Snippet: "敏感肌最近泛紅不適，想找舒緩修護", PublishedAt: time.Now().UnixNano()}}, "crawler", nil
	})
	w, err := svc.CreateWatch(context.Background(), 42, WatchInput{Terms: []string{"敏感肌"}, Enabled: true, BrandID: "b1", ProductID: "p1"})
	if err != nil {
		t.Fatal(err)
	}
	result, err := svc.RunSweep(context.Background(), 42, w.ID, "product-job-1")
	if err != nil {
		t.Fatal(err)
	}
	if result.Created != 1 {
		t.Fatalf("created=%d, want one", result.Created)
	}
	opps, _, err := svc.Repo.ListOpportunities(context.Background(), 42, domain.OpportunityListFilter{})
	if err != nil || len(opps) != 1 || len(opps[0].ProductMatches) != 1 {
		t.Fatalf("product pipeline output: %+v err=%v", opps, err)
	}
	_ = source
}

func TestProductSweepInvalidContextPausesWatchAndDoesNotSucceed(t *testing.T) {
	svc, source := productWatchService()
	w, err := svc.CreateWatch(context.Background(), 42, WatchInput{Terms: []string{"敏感肌"}, Enabled: true, BrandID: "b1", ProductID: "p1"})
	if err != nil {
		t.Fatal(err)
	}
	source.product = nil
	if _, err := svc.RunSweep(context.Background(), 42, w.ID, "product-job-invalid"); err == nil || !errors.Is(err, domain.ErrNotFound) {
		t.Fatalf("invalid product sweep err=%v", err)
	}
	got, err := svc.GetWatch(context.Background(), 42, w.ID)
	if err != nil {
		t.Fatal(err)
	}
	if got.Status != domain.WatchPaused || got.PauseReason != domain.PauseReasonProductUnavailable {
		t.Fatalf("invalid context did not pause watch: %+v", got)
	}
}

func TestProductExploreUsesSameJudgementPipeline(t *testing.T) {
	svc, _ := productWatchService()
	svc.HitFetch = HitFetcherFunc(func(context.Context, int64, []string, int) ([]ThreadHit, string, error) {
		return []ThreadHit{{URL: "https://threads.net/post/product-explore", Snippet: "敏感肌有人推薦舒緩修護嗎", PublishedAt: time.Now().UnixNano()}}, "crawler", nil
	})
	res, err := svc.ExploreProductOpportunities(context.Background(), 42, []string{"敏感肌"}, "b1", "p1")
	if err != nil {
		t.Fatal(err)
	}
	if res.CreatedCount != 1 {
		t.Fatalf("explore created=%d, want one", res.CreatedCount)
	}
}

func TestProductImportCarriesContextAndPreservesPartialRows(t *testing.T) {
	svc, _ := productWatchService()
	results, err := svc.ImportManualProductOpportunities(context.Background(), 42, []ManualImportItem{
		{URL: "https://threads.net/post/import-product", Text: "敏感肌想找舒緩修護"},
		{URL: "not-a-url", Text: "這列故意失敗"},
	}, "b1", "p1")
	if err != nil {
		t.Fatal(err)
	}
	if len(results) != 2 || results[0].Status == ImportStatusFailed || results[1].Status != ImportStatusFailed {
		t.Fatalf("partial import statuses: %+v", results)
	}
	opps, _, err := svc.Repo.ListOpportunities(context.Background(), 42, domain.OpportunityListFilter{})
	if err != nil || len(opps) != 1 || len(opps[0].ProductMatches) != 1 {
		t.Fatalf("import product match: %+v err=%v", opps, err)
	}
}

func TestProductSweepCountersSeparateEvaluatedMergedAndFitRejected(t *testing.T) {
	svc, source := productWatchService()
	svc.HitFetch = HitFetcherFunc(func(context.Context, int64, []string, int) ([]ThreadHit, string, error) {
		return []ThreadHit{{URL: "https://threads.net/post/counter", Snippet: "敏感肌泛紅不適", PublishedAt: time.Now().UnixNano()}}, "crawler", nil
	})
	ctx := context.Background()
	w1, err := svc.CreateWatch(ctx, 42, WatchInput{Terms: []string{"敏感肌"}, Enabled: true, BrandID: "b1", ProductID: "p1"})
	if err != nil {
		t.Fatal(err)
	}
	first, err := svc.RunSweep(ctx, 42, w1.ID, "counter-1")
	if err != nil {
		t.Fatal(err)
	}
	if first.Sweep.MatchEvaluatedCount != 1 || first.Sweep.FitRejectedCount != 1 || first.Sweep.MatchMergedCount != 0 {
		t.Fatalf("first counters: %+v", first.Sweep)
	}
	// The same public post is now evaluated for a second product and merged
	// into the existing Opportunity rather than consuming a new-opportunity slot.
	source.product.ID, source.product.Label = "p2", "第二產品"
	w2, err := svc.CreateWatch(ctx, 42, WatchInput{Terms: []string{"敏感肌"}, Enabled: true, BrandID: "b1", ProductID: "p2"})
	if err != nil {
		t.Fatal(err)
	}
	second, err := svc.RunSweep(ctx, 42, w2.ID, "counter-2")
	if err != nil {
		t.Fatal(err)
	}
	if second.Created != 0 || second.Sweep.MatchEvaluatedCount != 1 || second.Sweep.MatchMergedCount != 1 {
		t.Fatalf("second counters: created=%d sweep=%+v", second.Created, second.Sweep)
	}
}
