package datanode

import (
	"archive/tar"
	"bytes"
	"errors"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"testing"
)

func TestNew_Good(t *testing.T) {
	dn := New()
	if dn == nil {
		t.Fatal("New() returned nil")
	}
	if dn.files == nil {
		t.Error("New() did not initialize the files map")
	}
}

func TestAddData_Good(t *testing.T) {
	dn := New()
	path := "foo.txt"
	data := []byte("foo")
	dn.AddData(path, data)

	file, ok := dn.files[path]
	if !ok {
		t.Fatalf("file %q not found in datanode", path)
	}
	if string(file.content) != string(data) {
		t.Errorf("expected data %q, got %q", data, file.content)
	}
	info, err := file.Stat()
	if err != nil {
		t.Fatalf("file.Stat() failed: %v", err)
	}
	if info.Name() != "foo.txt" {
		t.Errorf("expected name foo.txt, got %s", info.Name())
	}
}

func TestAddData_Ugly(t *testing.T) {
	t.Run("Overwrite", func(t *testing.T) {
		dn := New()
		dn.AddData("foo.txt", []byte("foo"))
		dn.AddData("foo.txt", []byte("bar"))

		file, _ := dn.files["foo.txt"]
		if string(file.content) != "bar" {
			t.Errorf("expected data to be overwritten to 'bar', got %q", file.content)
		}
	})

	t.Run("Weird Path", func(t *testing.T) {
		dn := New()
		// path.Clean treats "a/../b/./c.txt" as "b/c.txt" but our implementation is simpler
		// and doesn't handle `..`. Let's test what it does handle.
		path := "./b/./c.txt"
		dn.AddData(path, []byte("c"))
		if _, ok := dn.files["./b/./c.txt"]; !ok {
			t.Errorf("expected path to be stored as is")
		}
	})
}

func TestOpen_Good(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("foo"))
	file, err := dn.Open("foo.txt")
	if err != nil {
		t.Fatalf("Open failed: %v", err)
	}
	defer file.Close()
}

func TestOpen_Bad(t *testing.T) {
	dn := New()
	_, err := dn.Open("nonexistent.txt")
	if err == nil {
		t.Fatal("expected error opening nonexistent file, got nil")
	}
	if !errors.Is(err, fs.ErrNotExist) {
		t.Errorf("expected fs.ErrNotExist, got %v", err)
	}
}

func TestOpen_Ugly(t *testing.T) {
	dn := New()
	dn.AddData("bar/baz.txt", []byte("baz"))
	file, err := dn.Open("bar") // Opening a directory
	if err != nil {
		t.Fatalf("expected no error when opening a directory, got %v", err)
	}
	defer file.Close()

	// Reading from a directory should fail
	_, err = file.Read(make([]byte, 1))
	if err == nil {
		t.Fatal("expected error reading from a directory, got nil")
	}
	var pathErr *fs.PathError
	if !errors.As(err, &pathErr) || pathErr.Err != fs.ErrInvalid {
		t.Errorf("expected fs.ErrInvalid when reading a directory, got %v", err)
	}
}

func TestStat_Good(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("foo"))
	dn.AddData("bar/baz.txt", []byte("baz"))

	// Test file
	info, err := dn.Stat("bar/baz.txt")
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	if info.Name() != "baz.txt" {
		t.Errorf("expected name baz.txt, got %s", info.Name())
	}
	if info.Size() != 3 {
		t.Errorf("expected size 3, got %d", info.Size())
	}
	if info.IsDir() {
		t.Error("expected baz.txt to not be a directory")
	}

	// Test directory
	dirInfo, err := dn.Stat("bar")
	if err != nil {
		t.Fatalf("Stat directory failed: %v", err)
	}
	if !dirInfo.IsDir() {
		t.Error("expected 'bar' to be a directory")
	}
	if dirInfo.Name() != "bar" {
		t.Errorf("expected dir name 'bar', got %s", dirInfo.Name())
	}
}

func TestStat_Bad(t *testing.T) {
	dn := New()
	_, err := dn.Stat("nonexistent")
	if err == nil {
		t.Fatal("expected error stating nonexistent file, got nil")
	}
	if !errors.Is(err, fs.ErrNotExist) {
		t.Errorf("expected fs.ErrNotExist, got %v", err)
	}
}

func TestStat_Ugly(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("foo"))

	// Test root
	info, err := dn.Stat(".")
	if err != nil {
		t.Fatalf("Stat('.') failed: %v", err)
	}
	if !info.IsDir() {
		t.Error("expected '.' to be a directory")
	}
	if info.Name() != "." {
		t.Errorf("expected name '.', got %s", info.Name())
	}
}

func TestExists_Good(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("foo"))
	dn.AddData("bar/baz.txt", []byte("baz"))

	exists, err := dn.Exists("foo.txt")
	if err != nil || !exists {
		t.Errorf("expected foo.txt to exist, err: %v", err)
	}

	exists, err = dn.Exists("bar")
	if err != nil || !exists {
		t.Errorf("expected 'bar' directory to exist, err: %v", err)
	}
}

func TestExists_Bad(t *testing.T) {
	dn := New()
	exists, err := dn.Exists("nonexistent")
	if err != nil {
		t.Errorf("unexpected error for nonexistent file: %v", err)
	}
	if exists {
		t.Error("expected 'nonexistent' to not exist")
	}
}

func TestExists_Ugly(t *testing.T) {
	dn := New()
	dn.AddData("dummy.txt", []byte("dummy"))
	// Test root
	exists, err := dn.Exists(".")
	if err != nil || !exists {
		t.Error("expected root '.' to exist")
	}
	// Test empty path
	exists, err = dn.Exists("")
	if err != nil {
		// our stat treats "" as "."
		if !strings.Contains(err.Error(), "exists") {
			t.Errorf("unexpected error for empty path: %v", err)
		}
	}
	if !exists {
		t.Error("expected empty path '' to exist (as root)")
	}
}

func TestReadDir_Good(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("foo"))
	dn.AddData("bar/baz.txt", []byte("baz"))
	dn.AddData("bar/qux.txt", []byte("qux"))

	// Read root
	entries, err := dn.ReadDir(".")
	if err != nil {
		t.Fatalf("ReadDir failed: %v", err)
	}
	expectedRootEntries := []string{"bar", "foo.txt"}
	entryNames := toSortedNames(entries)
	if !reflect.DeepEqual(entryNames, expectedRootEntries) {
		t.Errorf("expected entries %v, got %v", expectedRootEntries, entryNames)
	}

	// Read subdirectory
	barEntries, err := dn.ReadDir("bar")
	if err != nil {
		t.Fatalf("ReadDir('bar') failed: %v", err)
	}
	expectedBarEntries := []string{"baz.txt", "qux.txt"}
	barEntryNames := toSortedNames(barEntries)
	if !reflect.DeepEqual(barEntryNames, expectedBarEntries) {
		t.Errorf("expected entries %v, got %v", expectedBarEntries, barEntryNames)
	}
}

func TestReadDir_Bad(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("foo"))

	// Read nonexistent dir
	entries, err := dn.ReadDir("nonexistent")
	if err != nil {
		t.Fatalf("expected no error reading nonexistent dir, got %v", err)
	}
	if len(entries) != 0 {
		t.Errorf("expected 0 entries for nonexistent dir, got %d", len(entries))
	}

	// Read file
	_, err = dn.ReadDir("foo.txt")
	if err == nil {
		t.Fatal("expected error reading a file")
	}
	var pathErr *fs.PathError
	if !errors.As(err, &pathErr) || pathErr.Err != fs.ErrInvalid {
		t.Errorf("expected fs.ErrInvalid when reading a file, got %v", err)
	}
}

func TestReadDir_Ugly(t *testing.T) {
	dn := New()
	dn.AddData("bar/baz.txt", []byte("baz"))
	dn.AddData("empty_dir/", nil)

	// Read dir with another dir but no files
	entries, err := dn.ReadDir(".")
	if err != nil {
		t.Fatalf("ReadDir failed: %v", err)
	}
	expected := []string{"bar"} // empty_dir/ is ignored by AddData
	names := toSortedNames(entries)
	if !reflect.DeepEqual(names, expected) {
		t.Errorf("expected %v, got %v", expected, names)
	}
}

func TestWalk_Good(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("foo"))
	dn.AddData("bar/baz.txt", []byte("baz"))
	dn.AddData("bar/qux.txt", []byte("qux"))

	var paths []string
	dn.Walk(".", func(path string, d fs.DirEntry, err error) error {
		paths = append(paths, path)
		return nil
	})
	expectedPaths := []string{".", "bar", "bar/baz.txt", "bar/qux.txt", "foo.txt"}
	sort.Strings(paths)
	if !reflect.DeepEqual(paths, expectedPaths) {
		t.Errorf("Walk expected paths %v, got %v", expectedPaths, paths)
	}
}

func TestWalk_Bad(t *testing.T) {
	dn := New()
	// Walk non-existent path. fs.WalkDir will call the func with the error.
	var called bool
	err := dn.Walk("nonexistent", func(path string, d fs.DirEntry, err error) error {
		called = true
		if err == nil {
			t.Error("expected error for nonexistent path")
		}
		if !errors.Is(err, fs.ErrNotExist) {
			t.Errorf("unexpected error message: %v", err)
		}
		return err // propagate error
	})
	if !called {
		t.Fatal("walk function was not called for nonexistent root")
	}
	if !errors.Is(err, fs.ErrNotExist) {
		t.Errorf("expected Walk to return fs.ErrNotExist, got %v", err)
	}
}

func TestWalk_Ugly(t *testing.T) {
	dn := New()
	dn.AddData("a/b.txt", []byte("b"))
	dn.AddData("a/c.txt", []byte("c"))

	// Test stopping walk
	walkErr := errors.New("stop walking")
	var paths []string
	err := dn.Walk(".", func(path string, d fs.DirEntry, err error) error {
		if path == "a/b.txt" {
			return walkErr
		}
		paths = append(paths, path)
		return nil
	})

	if err != walkErr {
		t.Errorf("expected walk to return the callback error, got %v", err)
	}
}

func TestWalk_Options(t *testing.T) {
	dn := New()
	dn.AddData("root.txt", []byte("root"))
	dn.AddData("a/a1.txt", []byte("a1"))
	dn.AddData("a/b/b1.txt", []byte("b1"))
	dn.AddData("c/c1.txt", []byte("c1"))

	t.Run("MaxDepth", func(t *testing.T) {
		var paths []string
		err := dn.Walk(".", func(path string, d fs.DirEntry, err error) error {
			paths = append(paths, path)
			return nil
		}, WalkOptions{MaxDepth: 1})
		if err != nil {
			t.Fatalf("Walk failed: %v", err)
		}
		expected := []string{".", "a", "c", "root.txt"}
		sort.Strings(paths)
		if !reflect.DeepEqual(paths, expected) {
			t.Errorf("expected paths %v, got %v", expected, paths)
		}
	})

	t.Run("Filter", func(t *testing.T) {
		var paths []string
		err := dn.Walk(".", func(path string, d fs.DirEntry, err error) error {
			paths = append(paths, path)
			return nil
		}, WalkOptions{Filter: func(path string, d fs.DirEntry) bool {
			return !strings.HasPrefix(path, "a")
		}})
		if err != nil {
			t.Fatalf("Walk failed: %v", err)
		}
		expected := []string{".", "c", "c/c1.txt", "root.txt"}
		sort.Strings(paths)
		if !reflect.DeepEqual(paths, expected) {
			t.Errorf("expected paths %v, got %v", expected, paths)
		}
	})

	t.Run("SkipErrors", func(t *testing.T) {
		// Mock a walk failure by passing a non-existent root with SkipErrors.
		// Normally, WalkDir calls fn with an error for the root if it doesn't exist.
		var called bool
		err := dn.Walk("nonexistent", func(path string, d fs.DirEntry, err error) error {
			called = true
			return err
		}, WalkOptions{SkipErrors: true})

		if err != nil {
			t.Errorf("expected no error with SkipErrors, got %v", err)
		}
		if called {
			t.Error("callback should NOT be called if error is skipped internally")
		}
	})
}

func TestCopyFile_Good(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("foo"))

	tmpfile := filepath.Join(t.TempDir(), "test.txt")
	err := dn.CopyFile("foo.txt", tmpfile, 0644)
	if err != nil {
		t.Fatalf("CopyFile failed: %v", err)
	}

	content, err := os.ReadFile(tmpfile)
	if err != nil {
		t.Fatalf("ReadFile failed: %v", err)
	}
	if string(content) != "foo" {
		t.Errorf("expected foo, got %s", string(content))
	}
}

func TestCopyFile_Bad(t *testing.T) {
	dn := New()
	tmpfile := filepath.Join(t.TempDir(), "test.txt")

	// Source does not exist
	err := dn.CopyFile("nonexistent.txt", tmpfile, 0644)
	if err == nil {
		t.Fatal("expected error for nonexistent source file")
	}

	// Destination is not writable
	dn.AddData("foo.txt", []byte("foo"))
	err = dn.CopyFile("foo.txt", "/nonexistent_dir/test.txt", 0644)
	if err == nil {
		t.Fatal("expected error for unwritable destination")
	}
}

func TestCopyFile_Ugly(t *testing.T) {
	dn := New()
	dn.AddData("bar/baz.txt", []byte("baz"))
	tmpfile := filepath.Join(t.TempDir(), "test.txt")

	// Attempting to copy a directory
	err := dn.CopyFile("bar", tmpfile, 0644)
	if err == nil {
		t.Fatal("expected error when trying to copy a directory")
	}
}

func TestToTar_Good(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("foo"))
	dn.AddData("bar/baz.txt", []byte("baz"))

	tarball, err := dn.ToTar()
	if err != nil {
		t.Fatalf("ToTar failed: %v", err)
	}
	if len(tarball) == 0 {
		t.Fatal("expected non-empty tarball")
	}

	// Verify tar content
	tr := tar.NewReader(bytes.NewReader(tarball))
	files := make(map[string]string)
	for {
		header, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatalf("tar.Next failed: %v", err)
		}
		content, err := io.ReadAll(tr)
		if err != nil {
			t.Fatalf("read tar content failed: %v", err)
		}
		files[header.Name] = string(content)
	}

	if files["foo.txt"] != "foo" {
		t.Errorf("expected foo.txt content 'foo', got %q", files["foo.txt"])
	}
	if files["bar/baz.txt"] != "baz" {
		t.Errorf("expected bar/baz.txt content 'baz', got %q", files["bar/baz.txt"])
	}
}

func TestFromTar_Good(t *testing.T) {
	// Create a tarball
	buf := new(bytes.Buffer)
	tw := tar.NewWriter(buf)

	files := []struct{ Name, Body string }{
		{"foo.txt", "foo"},
		{"bar/baz.txt", "baz"},
	}
	for _, file := range files {
		hdr := &tar.Header{
			Name: file.Name,
			Mode: 0600,
			Size: int64(len(file.Body)),
			Typeflag: tar.TypeReg,
		}
		if err := tw.WriteHeader(hdr); err != nil {
			t.Fatalf("WriteHeader failed: %v", err)
		}
		if _, err := tw.Write([]byte(file.Body)); err != nil {
			t.Fatalf("Write failed: %v", err)
		}
	}
	if err := tw.Close(); err != nil {
		t.Fatalf("Close failed: %v", err)
	}

	dn, err := FromTar(buf.Bytes())
	if err != nil {
		t.Fatalf("FromTar failed: %v", err)
	}

	// Verify DataNode content
	exists, _ := dn.Exists("foo.txt")
	if !exists {
		t.Error("foo.txt missing")
	}
	exists, _ = dn.Exists("bar/baz.txt")
	if !exists {
		t.Error("bar/baz.txt missing")
	}
}

func TestTarRoundTrip_Good(t *testing.T) {
	dn1 := New()
	dn1.AddData("a.txt", []byte("a"))
	dn1.AddData("b/c.txt", []byte("c"))

	tarball, err := dn1.ToTar()
	if err != nil {
		t.Fatalf("ToTar failed: %v", err)
	}

	dn2, err := FromTar(tarball)
	if err != nil {
		t.Fatalf("FromTar failed: %v", err)
	}

	// Verify dn2 matches dn1
	exists, _ := dn2.Exists("a.txt")
	if !exists {
		t.Error("a.txt missing in dn2")
	}
	exists, _ = dn2.Exists("b/c.txt")
	if !exists {
		t.Error("b/c.txt missing in dn2")
	}
}

func TestFromTar_Bad(t *testing.T) {
	// Pass invalid data (truncated header)
	// A valid tar header is 512 bytes.
	truncated := make([]byte, 100)
	_, err := FromTar(truncated)
	if err == nil {
		t.Error("expected error for truncated tar header, got nil")
	} else if err != io.EOF && err != io.ErrUnexpectedEOF {
		// Verify it's some sort of read error or EOF related
		// Depending on implementation details of archive/tar
	}
}

func TestAddSymlink_Good(t *testing.T) {
	dn := New()
	dn.AddSymlink("link.txt", "target.txt")

	file, ok := dn.files["link.txt"]
	if !ok {
		t.Fatal("symlink not found in datanode")
	}
	if file.symlink != "target.txt" {
		t.Errorf("expected symlink target 'target.txt', got %q", file.symlink)
	}
	if !file.isSymlink() {
		t.Error("expected isSymlink() to return true")
	}

	// Stat should return ModeSymlink
	info, err := dn.Stat("link.txt")
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	if info.Mode()&os.ModeSymlink == 0 {
		t.Error("expected ModeSymlink to be set in file mode")
	}
}

func TestSymlinkTarRoundTrip_Good(t *testing.T) {
	dn1 := New()
	dn1.AddData("real.txt", []byte("real content"))
	dn1.AddSymlink("link.txt", "real.txt")

	tarball, err := dn1.ToTar()
	if err != nil {
		t.Fatalf("ToTar failed: %v", err)
	}

	// Verify the tar contains a symlink entry
	tr := tar.NewReader(bytes.NewReader(tarball))
	foundSymlink := false
	foundFile := false
	for {
		header, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatalf("tar.Next failed: %v", err)
		}
		switch header.Name {
		case "link.txt":
			foundSymlink = true
			if header.Typeflag != tar.TypeSymlink {
				t.Errorf("expected TypeSymlink, got %d", header.Typeflag)
			}
			if header.Linkname != "real.txt" {
				t.Errorf("expected Linkname 'real.txt', got %q", header.Linkname)
			}
			if header.Mode != 0777 {
				t.Errorf("expected mode 0777, got %o", header.Mode)
			}
		case "real.txt":
			foundFile = true
			if header.Typeflag != tar.TypeReg {
				t.Errorf("expected TypeReg for real.txt, got %d", header.Typeflag)
			}
		}
	}
	if !foundSymlink {
		t.Error("symlink entry not found in tarball")
	}
	if !foundFile {
		t.Error("regular file entry not found in tarball")
	}

	// Round-trip: FromTar should restore the symlink
	dn2, err := FromTar(tarball)
	if err != nil {
		t.Fatalf("FromTar failed: %v", err)
	}

	// Verify the regular file survived
	exists, _ := dn2.Exists("real.txt")
	if !exists {
		t.Error("real.txt missing after round-trip")
	}

	// Verify the symlink survived
	linkFile, ok := dn2.files["link.txt"]
	if !ok {
		t.Fatal("link.txt missing after round-trip")
	}
	if !linkFile.isSymlink() {
		t.Error("expected link.txt to be a symlink after round-trip")
	}
	if linkFile.symlink != "real.txt" {
		t.Errorf("expected symlink target 'real.txt', got %q", linkFile.symlink)
	}

	// Stat should still report ModeSymlink
	info, err := dn2.Stat("link.txt")
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	if info.Mode()&os.ModeSymlink == 0 {
		t.Error("expected ModeSymlink after round-trip")
	}
}

func TestAddSymlink_Bad(t *testing.T) {
	dn := New()

	// Empty name should be ignored
	dn.AddSymlink("", "target.txt")
	if len(dn.files) != 0 {
		t.Error("expected empty name to be ignored")
	}

	// Leading slash should be stripped
	dn.AddSymlink("/link.txt", "target.txt")
	if _, ok := dn.files["link.txt"]; !ok {
		t.Error("expected leading slash to be stripped")
	}

	// Directory-like name (trailing slash) should be ignored
	dn2 := New()
	dn2.AddSymlink("dir/", "target")
	if len(dn2.files) != 0 {
		t.Error("expected directory-like name to be ignored")
	}
}

func TestToTarWriter_Good(t *testing.T) {
	dn := New()
	dn.AddData("foo.txt", []byte("hello"))
	dn.AddData("bar/baz.txt", []byte("world"))

	var buf bytes.Buffer
	if err := dn.ToTarWriter(&buf); err != nil {
		t.Fatalf("ToTarWriter failed: %v", err)
	}

	// Round-trip through FromTar to verify contents survived.
	dn2, err := FromTar(buf.Bytes())
	if err != nil {
		t.Fatalf("FromTar failed: %v", err)
	}

	// Verify foo.txt
	f1, ok := dn2.files["foo.txt"]
	if !ok {
		t.Fatal("foo.txt missing after round-trip")
	}
	if string(f1.content) != "hello" {
		t.Errorf("expected foo.txt content 'hello', got %q", f1.content)
	}

	// Verify bar/baz.txt
	f2, ok := dn2.files["bar/baz.txt"]
	if !ok {
		t.Fatal("bar/baz.txt missing after round-trip")
	}
	if string(f2.content) != "world" {
		t.Errorf("expected bar/baz.txt content 'world', got %q", f2.content)
	}

	// Verify deterministic ordering: bar/baz.txt should come before foo.txt.
	tr := tar.NewReader(bytes.NewReader(buf.Bytes()))
	header1, err := tr.Next()
	if err != nil {
		t.Fatalf("tar.Next failed: %v", err)
	}
	header2, err := tr.Next()
	if err != nil {
		t.Fatalf("tar.Next failed: %v", err)
	}
	if header1.Name != "bar/baz.txt" || header2.Name != "foo.txt" {
		t.Errorf("expected sorted order [bar/baz.txt, foo.txt], got [%s, %s]",
			header1.Name, header2.Name)
	}
}

func TestToTarWriter_Symlinks_Good(t *testing.T) {
	dn := New()
	dn.AddData("real.txt", []byte("real content"))
	dn.AddSymlink("link.txt", "real.txt")

	var buf bytes.Buffer
	if err := dn.ToTarWriter(&buf); err != nil {
		t.Fatalf("ToTarWriter failed: %v", err)
	}

	// Round-trip through FromTar.
	dn2, err := FromTar(buf.Bytes())
	if err != nil {
		t.Fatalf("FromTar failed: %v", err)
	}

	// Verify regular file survived.
	realFile, ok := dn2.files["real.txt"]
	if !ok {
		t.Fatal("real.txt missing after round-trip")
	}
	if string(realFile.content) != "real content" {
		t.Errorf("expected 'real content', got %q", realFile.content)
	}

	// Verify symlink survived.
	linkFile, ok := dn2.files["link.txt"]
	if !ok {
		t.Fatal("link.txt missing after round-trip")
	}
	if !linkFile.isSymlink() {
		t.Error("expected link.txt to be a symlink")
	}
	if linkFile.symlink != "real.txt" {
		t.Errorf("expected symlink target 'real.txt', got %q", linkFile.symlink)
	}

	// Also verify the raw tar entries have correct types and modes.
	tr := tar.NewReader(bytes.NewReader(buf.Bytes()))
	for {
		header, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatalf("tar.Next failed: %v", err)
		}
		switch header.Name {
		case "link.txt":
			if header.Typeflag != tar.TypeSymlink {
				t.Errorf("expected TypeSymlink for link.txt, got %d", header.Typeflag)
			}
			if header.Linkname != "real.txt" {
				t.Errorf("expected Linkname 'real.txt', got %q", header.Linkname)
			}
			if header.Mode != 0777 {
				t.Errorf("expected mode 0777 for symlink, got %o", header.Mode)
			}
		case "real.txt":
			if header.Typeflag != tar.TypeReg {
				t.Errorf("expected TypeReg for real.txt, got %d", header.Typeflag)
			}
			if header.Mode != 0600 {
				t.Errorf("expected mode 0600 for regular file, got %o", header.Mode)
			}
		}
	}
}

func TestToTarWriter_Empty_Good(t *testing.T) {
	dn := New()

	var buf bytes.Buffer
	if err := dn.ToTarWriter(&buf); err != nil {
		t.Fatalf("ToTarWriter on empty DataNode should not error, got: %v", err)
	}

	// The buffer should contain a valid (empty) tar archive.
	dn2, err := FromTar(buf.Bytes())
	if err != nil {
		t.Fatalf("FromTar on empty tar failed: %v", err)
	}
	if len(dn2.files) != 0 {
		t.Errorf("expected 0 files in empty round-trip, got %d", len(dn2.files))
	}
}

func toSortedNames(entries []fs.DirEntry) []string {
	var names []string
	for _, e := range entries {
		names = append(names, e.Name())
	}
	sort.Strings(names)
	return names
}
