package repository

import (
	"context"
	"errors"
	"fmt"
	"testing"

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

func memoryRun(id string, owner int64, created int64, status string) *domain.Run {
	r := domain.NewRun(id, "job-"+id, owner, domain.RunBrief{
		Intent: "same topic", Mode: domain.ModeTheme, ThemeKey: "theme|same", ThemeLabel: "Same",
	}, created)
	r.Status = status
	if status == domain.RunSucceeded {
		r.EligibleCount = 3
	}
	return r
}

func TestMemoryRunSortingPagingAndOwnerIsolation(t *testing.T) {
	ctx := context.Background()
	store := NewMemory()
	if err := store.CreateRun(ctx, memoryRun("run-a", 1, 100, domain.RunSucceeded)); err != nil {
		t.Fatal(err)
	}
	if err := store.CreateRun(ctx, memoryRun("run-b", 1, 200, domain.RunSucceeded)); err != nil {
		t.Fatal(err)
	}
	if err := store.CreateRun(ctx, memoryRun("run-other", 2, 300, domain.RunSucceeded)); err != nil {
		t.Fatal(err)
	}
	page, err := store.ListRuns(ctx, domain.RunFilter{OwnerUID: 1, Page: 1, PageSize: 1})
	if err != nil || len(page.Items) != 1 || page.Items[0].ID != "run-b" || page.Pagination.Total != 2 {
		t.Fatalf("unexpected owner page: %+v, %v", page, err)
	}
	pageAgain, err := store.ListRuns(ctx, domain.RunFilter{OwnerUID: 1, Page: 1, PageSize: 1})
	if err != nil || pageAgain.Items[0].ID != page.Items[0].ID {
		t.Fatalf("non deterministic page: %+v, %v", pageAgain, err)
	}
	if _, err := store.GetRun(ctx, 2, "run-a"); !errors.Is(err, domain.ErrNotFound) {
		t.Fatalf("cross-owner run leaked: %v", err)
	}
}

func TestMemoryRunPostsHideStagedAndSortByPostedTime(t *testing.T) {
	ctx := context.Background()
	store := NewMemory()
	run := memoryRun("run-posts", 1, 100, domain.RunRunning)
	if err := store.CreateRun(ctx, run); err != nil {
		t.Fatal(err)
	}
	posts := []*domain.Post{
		{ID: "p-unknown", RunID: run.ID, OwnerUID: 1, PostedAt: 0, CreatedAt: 50},
		{ID: "p-old", RunID: run.ID, OwnerUID: 1, PostedAt: 100, CreatedAt: 10},
		{ID: "p-new", RunID: run.ID, OwnerUID: 1, PostedAt: 200, CreatedAt: 1},
	}
	if err := store.PublishRunPosts(ctx, 1, run.ID, posts); err != nil {
		t.Fatal(err)
	}
	hidden, err := store.ListRunPosts(ctx, 1, run.ID, 1, 2)
	if err != nil || hidden.Pagination.Total != 0 || len(hidden.Items) != 0 {
		t.Fatalf("staged posts leaked: %+v, %v", hidden, err)
	}
	run.Status = domain.RunSucceeded
	if err := store.ReplaceRunGuarded(ctx, 1, run.ID, []string{domain.RunRunning}, run); err != nil {
		t.Fatal(err)
	}
	page, err := store.ListRunPosts(ctx, 1, run.ID, 1, 2)
	if err != nil || len(page.Items) != 2 || page.Items[0].ID != "p-new" || page.Items[1].ID != "p-old" || page.Pagination.Total != 3 {
		t.Fatalf("unexpected post page: %+v, %v", page, err)
	}
	last, err := store.ListRunPosts(ctx, 1, run.ID, 2, 2)
	if err != nil || len(last.Items) != 1 || last.Items[0].ID != "p-unknown" {
		t.Fatalf("unknown-time ordering failed: %+v, %v", last, err)
	}
}

func TestMemoryRunGuardAndSeenIdentity(t *testing.T) {
	ctx := context.Background()
	store := NewMemory()
	run := memoryRun("run-guard", 1, 1, domain.RunQueued)
	if err := store.CreateRun(ctx, run); err != nil {
		t.Fatal(err)
	}
	run.Status = domain.RunSucceeded
	if err := store.ReplaceRunGuarded(ctx, 1, run.ID, []string{domain.RunQueued}, run); !errors.Is(err, domain.ErrIllegalRunStatus) {
		t.Fatalf("queued -> succeeded guard failed: %v", err)
	}
	if err := store.MarkSeenIdentity(ctx, 1, "canonical:one", "p-1", 10); err != nil {
		t.Fatal(err)
	}
	seen, err := store.HasSeenIdentity(ctx, 1, "canonical:one")
	if err != nil || !seen {
		t.Fatalf("seen identity missing: %v %v", seen, err)
	}
	other, err := store.HasSeenIdentity(ctx, 2, "canonical:one")
	if err != nil || other {
		t.Fatalf("seen identity leaked across owner: %v %v", other, err)
	}
}

func TestMemoryRunPagingLegacySyntheticAndCanonicalDedup(t *testing.T) {
	ctx := context.Background()
	store := NewMemory()
	for i := 0; i < 27; i++ {
		post := &domain.Post{
			ID: fmt.Sprintf("legacy-%02d", i), ExternalID: fmt.Sprintf("external-%02d", i),
			OwnerUID: 7, ThemeKey: "theme|legacy", ThemeLabel: "Legacy topic",
			OutreachStatus: domain.OutreachNew, PostedAt: int64(1_000 + i), CreatedAt: int64(2_000 + i),
		}
		if err := store.SavePost(ctx, post); err != nil {
			t.Fatal(err)
		}
	}
	if err := store.SavePost(ctx, &domain.Post{
		ID: "legacy-duplicate", ExternalID: "external-00", OwnerUID: 7,
		ThemeKey: "theme|legacy", ThemeLabel: "Legacy topic", PostedAt: 9_999, CreatedAt: 9_999,
	}); err != nil {
		t.Fatal(err)
	}
	runs, err := store.ListRuns(ctx, domain.RunFilter{OwnerUID: 7, Page: 1, PageSize: 10})
	if err != nil || len(runs.Items) != 1 || runs.Items[0].ID != domain.LegacyRunID("theme|legacy") || runs.Pagination.Total != 1 {
		t.Fatalf("legacy run not synthesized: %+v %v", runs, err)
	}
	seen := map[string]struct{}{}
	for page := 1; page <= 3; page++ {
		result, err := store.ListRunPosts(ctx, 7, runs.Items[0].ID, page, 10)
		if err != nil {
			t.Fatal(err)
		}
		for _, post := range result.Items {
			if _, exists := seen[post.ExternalID]; exists {
				t.Fatalf("duplicate crossed pages: %s", post.ExternalID)
			}
			seen[post.ExternalID] = struct{}{}
		}
	}
	if len(seen) != 27 {
		t.Fatalf("legacy unique total = %d, want 27", len(seen))
	}
}
