package usecase

import (
	"context"
	"testing"

	"git.d-ma.be/mathias/tapir/internal/domain"
)

// These tests pin the ADR-021 read-stored-first behaviour at the engine core:
// a stored transcript is summarized without re-touching the source, a miss
// fetches once and persists, and a transient rate-limit is never cached.

type recordingSource struct {
	transcript domain.Transcript
	fetchCalls int
}

func (s *recordingSource) ListSubscriptions(context.Context, string) ([]domain.Subscription, error) {
	return nil, nil
}

func (s *recordingSource) NewVideos(context.Context, domain.Subscription) ([]domain.Video, error) {
	return nil, nil
}

func (s *recordingSource) FetchTranscript(context.Context, domain.Video) (domain.Transcript, error) {
	s.fetchCalls++
	return s.transcript, nil
}

type fakeTranscriptStore struct {
	stored map[string]domain.Transcript
	saves  int
}

func newFakeTranscriptStore() *fakeTranscriptStore {
	return &fakeTranscriptStore{stored: make(map[string]domain.Transcript)}
}

func (f *fakeTranscriptStore) key(provider, id string) string { return provider + "|" + id }

func (f *fakeTranscriptStore) GetTranscript(_ context.Context, provider, id string) (domain.Transcript, bool, error) {
	t, ok := f.stored[f.key(provider, id)]
	return t, ok, nil
}

func (f *fakeTranscriptStore) SaveTranscript(_ context.Context, provider, id string, t domain.Transcript) error {
	f.saves++
	f.stored[f.key(provider, id)] = t
	return nil
}

type countingSummarizer struct{ calls int }

func (c *countingSummarizer) Summarize(_ context.Context, v domain.Video, _ domain.Transcript) (domain.Summary, error) {
	c.calls++
	return domain.Summary{VideoID: v.ID, UserID: v.UserID, Summary: "s", AIProvider: "local"}, nil
}

type nopSink struct{}

func (nopSink) Name() string                                  { return "nop" }
func (nopSink) Deliver(context.Context, domain.Summary) error { return nil }

func testVideo() domain.Video {
	return domain.Video{ID: "v1", UserID: "u1", Provider: domain.ProviderYouTube, ProviderVideoID: "yt1"}
}

func TestProcessNewVideo_StoredTranscriptSkipsFetch(t *testing.T) {
	src := &recordingSource{}
	ts := newFakeTranscriptStore()
	ts.stored[ts.key("youtube", "yt1")] = domain.Transcript{Source: domain.SourceCaptions, Content: "stored words"}
	sum := &countingSummarizer{}
	eng := NewEngine(src, sum, nopSink{})
	eng.Transcripts = ts

	res, err := eng.ProcessNewVideo(context.Background(), testVideo())
	if err != nil {
		t.Fatalf("ProcessNewVideo: %v", err)
	}
	if src.fetchCalls != 0 {
		t.Fatalf("stored transcript must not re-fetch from source; got %d fetches", src.fetchCalls)
	}
	if ts.saves != 0 {
		t.Fatalf("a store hit must not re-save; got %d saves", ts.saves)
	}
	if sum.calls != 1 || res.Summary == nil {
		t.Fatalf("expected a summary from the stored transcript; calls=%d summary=%v", sum.calls, res.Summary)
	}
}

func TestProcessNewVideo_StoreMissFetchesAndPersists(t *testing.T) {
	src := &recordingSource{transcript: domain.Transcript{Source: domain.SourceCaptions, Language: "en", Content: "fetched words"}}
	ts := newFakeTranscriptStore()
	sum := &countingSummarizer{}
	eng := NewEngine(src, sum, nopSink{})
	eng.Transcripts = ts

	if _, err := eng.ProcessNewVideo(context.Background(), testVideo()); err != nil {
		t.Fatalf("ProcessNewVideo: %v", err)
	}
	if src.fetchCalls != 1 {
		t.Fatalf("a store miss must fetch exactly once; got %d", src.fetchCalls)
	}
	if ts.saves != 1 {
		t.Fatalf("a fetched transcript must be persisted; got %d saves", ts.saves)
	}
	got, ok, _ := ts.GetTranscript(context.Background(), "youtube", "yt1")
	if !ok || got.Content != "fetched words" {
		t.Fatalf("persisted transcript not readable back: ok=%v content=%q", ok, got.Content)
	}
}

// The second summarize of the same video reads the persisted transcript and does
// NOT re-fetch — the primary ADR-021 win, proven end to end at the engine.
func TestProcessNewVideo_SecondSummarizeDoesNotRefetch(t *testing.T) {
	src := &recordingSource{transcript: domain.Transcript{Source: domain.SourceCaptions, Content: "words"}}
	ts := newFakeTranscriptStore()
	eng := NewEngine(src, &countingSummarizer{}, nopSink{})
	eng.Transcripts = ts

	for i := 0; i < 2; i++ {
		if _, err := eng.ProcessNewVideo(context.Background(), testVideo()); err != nil {
			t.Fatalf("pass %d: %v", i, err)
		}
	}
	if src.fetchCalls != 1 {
		t.Fatalf("the second summarize must reuse the stored transcript; got %d fetches", src.fetchCalls)
	}
}

// A stored "no captions" outcome short-circuits before both fetch and summarize.
func TestProcessNewVideo_StoredNoneSkipsFetchAndSummarize(t *testing.T) {
	src := &recordingSource{}
	ts := newFakeTranscriptStore()
	ts.stored[ts.key("youtube", "yt1")] = domain.Transcript{Source: domain.SourceNone}
	sum := &countingSummarizer{}
	eng := NewEngine(src, sum, nopSink{})
	eng.Transcripts = ts

	res, err := eng.ProcessNewVideo(context.Background(), testVideo())
	if err != nil {
		t.Fatalf("ProcessNewVideo: %v", err)
	}
	if !res.Skipped {
		t.Fatal("a stored SourceNone must skip")
	}
	if src.fetchCalls != 0 || sum.calls != 0 {
		t.Fatalf("stored none must neither fetch nor summarize; fetches=%d calls=%d", src.fetchCalls, sum.calls)
	}
}

// A transient 429 is surfaced (so the runner backs off per-user) but never cached
// as a shared terminal state — otherwise it would mask a rate-limit as permanent.
func TestProcessNewVideo_RateLimitedIsNotPersisted(t *testing.T) {
	src := &recordingSource{transcript: domain.Transcript{Source: domain.SourceRateLimited}}
	ts := newFakeTranscriptStore()
	eng := NewEngine(src, &countingSummarizer{}, nopSink{})
	eng.Transcripts = ts

	res, err := eng.ProcessNewVideo(context.Background(), testVideo())
	if err != nil {
		t.Fatalf("ProcessNewVideo: %v", err)
	}
	if !res.Skipped || res.TranscriptSource != string(domain.SourceRateLimited) {
		t.Fatalf("expected a rate-limited skip; skipped=%v source=%q", res.Skipped, res.TranscriptSource)
	}
	if ts.saves != 0 {
		t.Fatalf("a transient rate-limit must not be persisted; got %d saves", ts.saves)
	}
}

// With no TranscriptStore wired the engine fetches every time (back-compat).
func TestProcessNewVideo_NilStoreFetchesEveryTime(t *testing.T) {
	src := &recordingSource{transcript: domain.Transcript{Source: domain.SourceCaptions, Content: "words"}}
	eng := NewEngine(src, &countingSummarizer{}, nopSink{})

	for i := 0; i < 2; i++ {
		if _, err := eng.ProcessNewVideo(context.Background(), testVideo()); err != nil {
			t.Fatalf("pass %d: %v", i, err)
		}
	}
	if src.fetchCalls != 2 {
		t.Fatalf("nil store must fetch every time; got %d", src.fetchCalls)
	}
}
