package volsnap

import (
	"archive/tar"
	"compress/gzip"
	"io"
	"os"
	"path/filepath"
	"testing"
)

func TestWriteArchiveRoundTrip(t *testing.T) {
	root := t.TempDir()
	mustWrite(t, filepath.Join(root, "a.txt"), "hello")
	if err := os.MkdirAll(filepath.Join(root, "sub"), 0o755); err != nil {
		t.Fatal(err)
	}
	mustWrite(t, filepath.Join(root, "sub", "b.txt"), "world")

	dest := filepath.Join(t.TempDir(), "snap.tar.gz")
	refs := []VolumeRef{{Target: "/data", Scope: "project", Source: "data", HostPath: root}}

	manifest, err := writeArchive(dest, refs)
	if err != nil {
		t.Fatalf("writeArchive: %v", err)
	}
	if len(manifest) != 1 || manifest[0].Index != 0 || manifest[0].Target != "/data" || manifest[0].Scope != "project" {
		t.Fatalf("unexpected manifest: %+v", manifest)
	}

	entries := readArchive(t, dest)
	for _, want := range []string{"0/a.txt", "0/sub/b.txt", "manifest.json"} {
		if _, ok := entries[want]; !ok {
			keys := make([]string, 0, len(entries))
			for k := range entries {
				keys = append(keys, k)
			}
			t.Fatalf("archive missing %q; got %v", want, keys)
		}
	}
	if got := entries["0/a.txt"]; got != "hello" {
		t.Errorf("0/a.txt = %q, want %q", got, "hello")
	}
}

func TestWriteArchiveRefusesExisting(t *testing.T) {
	dest := filepath.Join(t.TempDir(), "snap.tar.gz")
	mustWrite(t, dest, "existing")
	if _, err := writeArchive(dest, nil); err == nil {
		t.Fatal("expected error writing over an existing file (O_EXCL)")
	}
}

func TestWriteArchiveSkipsSymlinks(t *testing.T) {
	root := t.TempDir()
	mustWrite(t, filepath.Join(root, "real.txt"), "data")
	if err := os.Symlink(filepath.Join(root, "real.txt"), filepath.Join(root, "link.txt")); err != nil {
		t.Skipf("symlinks unavailable on this platform: %v", err)
	}

	dest := filepath.Join(t.TempDir(), "snap.tar.gz")
	if _, err := writeArchive(dest, []VolumeRef{{Target: "/d", Scope: "project", HostPath: root}}); err != nil {
		t.Fatalf("writeArchive: %v", err)
	}
	entries := readArchive(t, dest)
	if _, ok := entries["0/link.txt"]; ok {
		t.Error("symlink should have been skipped, but it is in the archive")
	}
	if _, ok := entries["0/real.txt"]; !ok {
		t.Error("regular file should be archived")
	}
}

func mustWrite(t *testing.T, path, content string) {
	t.Helper()
	if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
		t.Fatal(err)
	}
}

// readArchive returns a map of regular-file entry name -> content. Directory
// entries are recorded with an empty string so their presence can be asserted.
func readArchive(t *testing.T, path string) map[string]string {
	t.Helper()
	f, err := os.Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()
	gz, err := gzip.NewReader(f)
	if err != nil {
		t.Fatal(err)
	}
	defer gz.Close()

	out := map[string]string{}
	tr := tar.NewReader(gz)
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatal(err)
		}
		if hdr.Typeflag == tar.TypeDir {
			out[hdr.Name] = ""
			continue
		}
		data, err := io.ReadAll(tr)
		if err != nil {
			t.Fatal(err)
		}
		out[hdr.Name] = string(data)
	}
	return out
}
