package taskqueue_test

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/libnovel/backend/internal/domain"
	"github.com/libnovel/backend/internal/taskqueue"
)

// ── Compile-time interface satisfaction ───────────────────────────────────────

// stubStore satisfies all three taskqueue interfaces.
// Any method that is called but not expected panics — making accidental
// calls immediately visible in tests.
type stubStore struct{}

func (s *stubStore) CreateScrapeTask(_ context.Context, _, _ string, _, _ int) (string, error) {
	return "task-1", nil
}
func (s *stubStore) CreateAudioTask(_ context.Context, _ string, _ int, _ string) (string, error) {
	return "audio-1", nil
}
func (s *stubStore) CreateTranslationTask(_ context.Context, _ string, _ int, _ string) (string, error) {
	return "translation-1", nil
}
func (s *stubStore) CreateImportTask(_ context.Context, _, _, _, _, _ string) (string, error) {
	return "import-1", nil
}
func (s *stubStore) CancelTask(_ context.Context, _ string) error                    { return nil }
func (s *stubStore) CancelAudioTasksBySlug(_ context.Context, _ string) (int, error) { return 0, nil }

func (s *stubStore) ClaimNextScrapeTask(_ context.Context, _ string) (domain.ScrapeTask, bool, error) {
	return domain.ScrapeTask{ID: "task-1", Status: domain.TaskStatusRunning}, true, nil
}
func (s *stubStore) ClaimNextAudioTask(_ context.Context, _ string) (domain.AudioTask, bool, error) {
	return domain.AudioTask{ID: "audio-1", Status: domain.TaskStatusRunning}, true, nil
}
func (s *stubStore) ClaimNextTranslationTask(_ context.Context, _ string) (domain.TranslationTask, bool, error) {
	return domain.TranslationTask{ID: "translation-1", Status: domain.TaskStatusRunning}, true, nil
}
func (s *stubStore) ClaimNextImportTask(_ context.Context, _ string) (domain.ImportTask, bool, error) {
	return domain.ImportTask{ID: "import-1", Status: domain.TaskStatusRunning}, true, nil
}
func (s *stubStore) FinishScrapeTask(_ context.Context, _ string, _ domain.ScrapeResult) error {
	return nil
}
func (s *stubStore) FinishAudioTask(_ context.Context, _ string, _ domain.AudioResult) error {
	return nil
}
func (s *stubStore) FinishTranslationTask(_ context.Context, _ string, _ domain.TranslationResult) error {
	return nil
}
func (s *stubStore) FinishImportTask(_ context.Context, _ string, _ domain.ImportResult) error {
	return nil
}
func (s *stubStore) FailTask(_ context.Context, _, _ string) error { return nil }

func (s *stubStore) HeartbeatTask(_ context.Context, _ string) error { return nil }

func (s *stubStore) ReapStaleTasks(_ context.Context, _ time.Duration) (int, error) {
	return 0, nil
}

func (s *stubStore) ListScrapeTasks(_ context.Context) ([]domain.ScrapeTask, error) { return nil, nil }
func (s *stubStore) GetScrapeTask(_ context.Context, _ string) (domain.ScrapeTask, bool, error) {
	return domain.ScrapeTask{}, false, nil
}
func (s *stubStore) ListAudioTasks(_ context.Context) ([]domain.AudioTask, error) { return nil, nil }
func (s *stubStore) GetAudioTask(_ context.Context, _ string) (domain.AudioTask, bool, error) {
	return domain.AudioTask{}, false, nil
}
func (s *stubStore) ListTranslationTasks(_ context.Context) ([]domain.TranslationTask, error) {
	return nil, nil
}
func (s *stubStore) GetTranslationTask(_ context.Context, _ string) (domain.TranslationTask, bool, error) {
	return domain.TranslationTask{}, false, nil
}
func (s *stubStore) ListImportTasks(_ context.Context) ([]domain.ImportTask, error) { return nil, nil }
func (s *stubStore) GetImportTask(_ context.Context, _ string) (domain.ImportTask, bool, error) {
	return domain.ImportTask{}, false, nil
}

// Verify the stub satisfies all three interfaces at compile time.
var _ taskqueue.Producer = (*stubStore)(nil)
var _ taskqueue.Consumer = (*stubStore)(nil)
var _ taskqueue.Reader = (*stubStore)(nil)

// ── Behavioural tests (using stub) ────────────────────────────────────────────

func TestProducer_CreateScrapeTask(t *testing.T) {
	var p taskqueue.Producer = &stubStore{}
	id, err := p.CreateScrapeTask(context.Background(), "book", "https://example.com/book/slug", 0, 0)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if id == "" {
		t.Error("expected non-empty task ID")
	}
}

func TestConsumer_ClaimNextScrapeTask(t *testing.T) {
	var c taskqueue.Consumer = &stubStore{}
	task, ok, err := c.ClaimNextScrapeTask(context.Background(), "worker-1")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !ok {
		t.Fatal("expected a task to be claimed")
	}
	if task.Status != domain.TaskStatusRunning {
		t.Errorf("want running, got %q", task.Status)
	}
}

func TestConsumer_ClaimNextAudioTask(t *testing.T) {
	var c taskqueue.Consumer = &stubStore{}
	task, ok, err := c.ClaimNextAudioTask(context.Background(), "worker-1")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !ok {
		t.Fatal("expected an audio task to be claimed")
	}
	if task.ID == "" {
		t.Error("expected non-empty task ID")
	}
}

// ── domain.ScrapeResult / domain.AudioResult JSON shape ──────────────────────

func TestScrapeResult_JSONRoundtrip(t *testing.T) {
	cases := []domain.ScrapeResult{
		{BooksFound: 5, ChaptersScraped: 100, ChaptersSkipped: 2, Errors: 0},
		{BooksFound: 0, ChaptersScraped: 0, Errors: 1, ErrorMessage: "timeout"},
	}
	for _, orig := range cases {
		b, err := json.Marshal(orig)
		if err != nil {
			t.Fatalf("marshal: %v", err)
		}
		var got domain.ScrapeResult
		if err := json.Unmarshal(b, &got); err != nil {
			t.Fatalf("unmarshal: %v", err)
		}
		if got != orig {
			t.Errorf("want %+v, got %+v", orig, got)
		}
	}
}

func TestAudioResult_JSONRoundtrip(t *testing.T) {
	cases := []domain.AudioResult{
		{ObjectKey: "audio/slug/1/af_bella.mp3"},
		{ErrorMessage: "kokoro unavailable"},
	}
	for _, orig := range cases {
		b, _ := json.Marshal(orig)
		var got domain.AudioResult
		json.Unmarshal(b, &got)
		if got != orig {
			t.Errorf("want %+v, got %+v", orig, got)
		}
	}
}
