package photosync

import (
	"context"
	"io"
	"path/filepath"
	"strings"
	"testing"

	"git.azzamo.net/Michilis/Spanglish/photo-api/internal/storage"
	"git.azzamo.net/Michilis/Spanglish/photo-api/internal/store"
)

// Two local backends stand in for the real pair: copyObject only talks to the
// storage.Storage interface, so the S3 side needs no fake here.
func backends(t *testing.T) (src, dst storage.Storage, srcRoot string) {
	t.Helper()
	dir := t.TempDir()
	srcRoot = filepath.Join(dir, "src")
	src, err := storage.NewLocal(srcRoot)
	if err != nil {
		t.Fatal(err)
	}
	dst, err = storage.NewLocal(filepath.Join(dir, "dst"))
	if err != nil {
		t.Fatal(err)
	}
	return src, dst, srcRoot
}

func put(t *testing.T, s storage.Storage, key, body string) {
	t.Helper()
	if err := s.Put(context.Background(), key, strings.NewReader(body), int64(len(body)), "image/jpeg"); err != nil {
		t.Fatal(err)
	}
}

const testKey = "galleries/g1/original/p1.jpg"

func testObject() store.PhotoObject {
	return store.PhotoObject{PhotoID: "p1", GalleryID: "g1", Variant: "original", Key: testKey, ContentType: "image/jpeg"}
}

func TestCopyObject(t *testing.T) {
	ctx := context.Background()

	t.Run("copies to destination", func(t *testing.T) {
		src, dst, _ := backends(t)
		put(t, src, testKey, "hello-photo")

		act, size, err := copyObject(ctx, src, dst, testObject(), Options{})
		if err != nil || act != actionCopied || size != 11 {
			t.Fatalf("got (%v, %d, %v), want (copied, 11, nil)", act, size, err)
		}
		if got, err := dst.Stat(ctx, testKey); err != nil || got != 11 {
			t.Fatalf("destination stat: (%d, %v)", got, err)
		}
	})

	t.Run("skips same-size object already present", func(t *testing.T) {
		src, dst, _ := backends(t)
		put(t, src, testKey, "hello-photo")
		put(t, dst, testKey, "hello-photo")

		act, _, err := copyObject(ctx, src, dst, testObject(), Options{})
		if err != nil || act != actionSkipped {
			t.Fatalf("got (%v, %v), want (skipped, nil)", act, err)
		}
	})

	t.Run("overwrite re-copies", func(t *testing.T) {
		src, dst, _ := backends(t)
		put(t, src, testKey, "new-content")
		put(t, dst, testKey, "old-content")

		act, _, err := copyObject(ctx, src, dst, testObject(), Options{Overwrite: true})
		if err != nil || act != actionCopied {
			t.Fatalf("got (%v, %v), want (copied, nil)", act, err)
		}
		r, _, err := dst.Open(ctx, testKey)
		if err != nil {
			t.Fatal(err)
		}
		defer r.Close()
		body, err := io.ReadAll(r)
		if err != nil {
			t.Fatal(err)
		}
		if string(body) != "new-content" {
			t.Fatalf("destination body = %q, want new-content", body)
		}
	})

	t.Run("reports objects missing on the source", func(t *testing.T) {
		src, dst, _ := backends(t)

		act, _, err := copyObject(ctx, src, dst, testObject(), Options{})
		if err != nil || act != actionMissing {
			t.Fatalf("got (%v, %v), want (missing, nil)", act, err)
		}
		if _, err := dst.Stat(ctx, testKey); err != storage.ErrNotExist {
			t.Fatalf("destination stat err = %v, want ErrNotExist", err)
		}
	})

	t.Run("dry run writes nothing", func(t *testing.T) {
		src, dst, _ := backends(t)
		put(t, src, testKey, "hello-photo")

		act, size, err := copyObject(ctx, src, dst, testObject(), Options{DryRun: true})
		if err != nil || act != actionCopied || size != 11 {
			t.Fatalf("got (%v, %d, %v), want (copied, 11, nil)", act, size, err)
		}
		if _, err := dst.Stat(ctx, testKey); err != storage.ErrNotExist {
			t.Fatalf("destination stat err = %v, want ErrNotExist", err)
		}
	})
}

func TestParseDirection(t *testing.T) {
	for in, want := range map[string]Direction{
		"to-s3":       ToS3,
		"local-to-s3": ToS3,
		"to-local":    ToLocal,
		"s3-to-local": ToLocal,
	} {
		got, err := ParseDirection(in)
		if err != nil || got != want {
			t.Errorf("ParseDirection(%q) = (%v, %v), want %v", in, got, err, want)
		}
	}
	if _, err := ParseDirection("sideways"); err == nil {
		t.Error("ParseDirection(\"sideways\") should fail")
	}
}
