package jobs

import (
	"context"
	"errors"
	"io"
	"testing"

	"backend/internal/db/sqlc"

	"github.com/google/uuid"
	"github.com/riverqueue/river"
	"github.com/riverqueue/river/rivertype"
)

// mockQuerier implements fileQuerier for unit tests.
type mockQuerier struct {
	orphans          []sqlc.ListOrphanFilesRow
	listErr          error
	deleteFileCalls  []sqlc.DeleteTabloFileParams
	deleteFileErr    error
}

func (m *mockQuerier) ListOrphanFiles(_ context.Context) ([]sqlc.ListOrphanFilesRow, error) {
	return m.orphans, m.listErr
}

func (m *mockQuerier) DeleteTabloFile(_ context.Context, arg sqlc.DeleteTabloFileParams) error {
	m.deleteFileCalls = append(m.deleteFileCalls, arg)
	return m.deleteFileErr
}

// mockFileStorer implements files.FileStorer for unit tests.
type mockFileStorer struct {
	deletedKeys []string
	deleteErr   error
}

func (m *mockFileStorer) Delete(_ context.Context, key string) error {
	m.deletedKeys = append(m.deletedKeys, key)
	return m.deleteErr
}

func (m *mockFileStorer) Upload(_ context.Context, _ string, _ io.Reader) (string, int64, error) {
	return "", 0, errors.New("not implemented")
}

func (m *mockFileStorer) PresignDownload(_ context.Context, _ string) (string, error) {
	return "", errors.New("not implemented")
}

func makeWorkerJob() *river.Job[OrphanCleanupArgs] {
	return &river.Job[OrphanCleanupArgs]{
		JobRow: &rivertype.JobRow{ID: 1, Attempt: 1},
		Args:   OrphanCleanupArgs{},
	}
}

func TestOrphanCleanupWorker_NoOrphans(t *testing.T) {
	q := &mockQuerier{}
	store := &mockFileStorer{}
	w := &OrphanCleanupWorker{
		store:   store,
		querier: q,
	}

	if err := w.Work(context.Background(), makeWorkerJob()); err != nil {
		t.Fatalf("Work returned unexpected error: %v", err)
	}
	if len(store.deletedKeys) != 0 {
		t.Errorf("expected 0 S3 deletes, got %d", len(store.deletedKeys))
	}
	if len(q.deleteFileCalls) != 0 {
		t.Errorf("expected 0 DB deletes, got %d", len(q.deleteFileCalls))
	}
}

func TestOrphanCleanupWorker_DeletesOrphan(t *testing.T) {
	fileID := uuid.New()
	tabloID := uuid.New()
	q := &mockQuerier{
		orphans: []sqlc.ListOrphanFilesRow{
			{ID: fileID, TabloID: tabloID, S3Key: "orphan-key"},
		},
	}
	store := &mockFileStorer{}
	w := &OrphanCleanupWorker{
		store:   store,
		querier: q,
	}

	if err := w.Work(context.Background(), makeWorkerJob()); err != nil {
		t.Fatalf("Work returned unexpected error: %v", err)
	}

	// Assert S3 Delete was called with the correct key.
	if len(store.deletedKeys) != 1 || store.deletedKeys[0] != "orphan-key" {
		t.Errorf("expected S3 Delete called with %q, got %v", "orphan-key", store.deletedKeys)
	}

	// Assert DB DeleteTabloFile was called with matching ID and TabloID.
	if len(q.deleteFileCalls) != 1 {
		t.Fatalf("expected 1 DB delete call, got %d", len(q.deleteFileCalls))
	}
	got := q.deleteFileCalls[0]
	if got.ID != fileID || got.TabloID != tabloID {
		t.Errorf("DeleteTabloFile called with {%v, %v}, want {%v, %v}", got.ID, got.TabloID, fileID, tabloID)
	}
}

func TestOrphanCleanupArgs_Kind(t *testing.T) {
	if got := (OrphanCleanupArgs{}).Kind(); got != "orphan_file_cleanup" {
		t.Errorf("OrphanCleanupArgs.Kind() = %q, want %q", got, "orphan_file_cleanup")
	}
}
