package contentpayload

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"testing"
	"time"

	git "github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/filemode"
	"github.com/go-git/go-git/v5/plumbing/object"
	"github.com/stretchr/testify/require"
)

func TestGitTreeConcurrentBackendsCannotOverwriteImmutableVersion(t *testing.T) {
	root := t.TempDir()
	firstValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	secondValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, firstValue.Close()); require.NoError(t, secondValue.Close()) })
	seed, err := firstValue.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "seed", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("s")}}})
	require.NoError(t, err)
	start := make(chan struct{})
	results := make(chan error, 2)
	for index, backend := range []TreePayloadBackend{firstValue, secondValue} {
		index, backend := index, backend
		go func() {
			<-start
			_, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", ParentPayloadRef: seed.ManifestRef, Files: []File{{Path: "a", Size: 1, Content: bytes.NewBuffer([]byte{'a' + byte(index)})}}})
			results <- err
		}()
	}
	close(start)
	errs := []error{<-results, <-results}
	var succeeded, conflicted int
	for _, err := range errs {
		switch {
		case err == nil:
			succeeded++
		case errors.Is(err, ErrAlreadyExists):
			conflicted++
		default:
			require.NoError(t, err)
		}
	}
	require.Equal(t, 1, succeeded)
	require.Equal(t, 1, conflicted)
	data, err := os.ReadFile(filepath.Join(root, "ws", "ct.git", "refs", "content-versions", "v1"))
	require.NoError(t, err)
	require.Len(t, strings.TrimSpace(string(data)), 40)
}

func TestGitTreeStagesNestedExactImmutableVersion(t *testing.T) {
	b, err := NewGitTreeBackend(t.TempDir())
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, b.Close()) })
	s, err := b.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Actor: "actor", Files: []File{{Path: "IDENTITY.md", Size: 3, Content: bytes.NewBufferString("one")}, {Path: "skills/a/SKILL.md", Size: 3, Content: bytes.NewBufferString("two")}}})
	require.NoError(t, err)
	require.Contains(t, string(s.ManifestRef), "v1@")
	m, err := b.OpenManifest(context.Background(), s.ManifestRef)
	require.NoError(t, err)
	require.Len(t, m.Files, 2)
	body, _, err := b.OpenFile(context.Background(), s.ManifestRef, "skills/a/SKILL.md", nil)
	require.NoError(t, err)
	data, err := io.ReadAll(body)
	require.NoError(t, err)
	require.NoError(t, body.Close())
	require.Equal(t, "two", string(data))
	target := filepath.Join(t.TempDir(), "out")
	require.NoError(t, b.Materialize(context.Background(), s.ManifestRef, target, MaterializeLimits{}))
}

func TestGitTreeVersionsHaveExactCommitAndParent(t *testing.T) {
	root := t.TempDir()
	backend, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	first, err := backend.StageTree(context.Background(), StageTreeRequest{
		WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Actor: "actor",
		Files: []File{{Path: "a", Size: 3, Content: bytes.NewBufferString("one")}},
	})
	require.NoError(t, err)
	second, err := backend.StageTree(context.Background(), StageTreeRequest{
		WorkspaceID: "ws", ContentID: "ct", VersionID: "v2", Actor: "actor", ParentPayloadRef: first.ManifestRef,
		Files: []File{{Path: "a", Size: 3, Content: bytes.NewBufferString("two")}},
	})
	require.NoError(t, err)
	_, firstHashText, ok := strings.Cut(string(first.ManifestRef), "@")
	require.True(t, ok)
	_, secondHashText, ok := strings.Cut(string(second.ManifestRef), "@")
	require.True(t, ok)
	repo, err := git.PlainOpen(filepath.Join(root, "ws", "ct.git"))
	require.NoError(t, err)
	commit, err := repo.CommitObject(plumbing.NewHash(secondHashText))
	require.NoError(t, err)
	require.Equal(t, []plumbing.Hash{plumbing.NewHash(firstHashText)}, commit.ParentHashes)
	ref, err := repo.Reference("refs/content-versions/v2", true)
	require.NoError(t, err)
	require.Equal(t, plumbing.NewHash(secondHashText), ref.Hash())
}

func TestGitTreeCloseRejectsOperations(t *testing.T) {
	backend, err := NewGitTreeBackend(t.TempDir())
	require.NoError(t, err)
	require.NoError(t, backend.Close())
	require.NoError(t, backend.Close())
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.ErrorIs(t, err, errFilesystemClosed)
	_, err = backend.OpenManifest(context.Background(), "ws/ct/v1")
	require.ErrorIs(t, err, errFilesystemClosed)
}

func TestGitTreeRejectsSameVersionMismatchInvalidPathsLimitsAndRanges(t *testing.T) {
	backend, err := NewGitTreeBackend(t.TempDir())
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	request := StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 3, Content: bytes.NewBufferString("one")}}}
	first, err := backend.StageTree(context.Background(), request)
	require.NoError(t, err)
	request.Files[0].Content = bytes.NewBufferString("one")
	second, err := backend.StageTree(context.Background(), request)
	require.NoError(t, err)
	require.False(t, second.Created)
	require.Equal(t, first.ManifestRef, second.ManifestRef)
	request.Files[0].Content = bytes.NewBufferString("two")
	_, err = backend.StageTree(context.Background(), request)
	require.ErrorIs(t, err, ErrAlreadyExists)
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v2", Files: []File{{Path: "../escape", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.Error(t, err)
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v2", Limits: Limits{MaxFiles: 1, MaxFileBytes: 1, MaxTotalBytes: 1}, Files: []File{{Path: "a", Size: 2, Content: bytes.NewBufferString("xx")}}})
	require.Error(t, err)
	_, _, err = backend.OpenFile(context.Background(), first.ManifestRef, "a", &ByteRange{Offset: 2, Length: 2})
	require.Error(t, err)
}

func TestGitTreeRejectsWorkspaceAndRepositorySymlinks(t *testing.T) {
	for _, target := range []string{"workspace", "repository"} {
		t.Run(target, func(t *testing.T) {
			root := t.TempDir()
			outside := t.TempDir()
			if target == "workspace" {
				require.NoError(t, os.Symlink(outside, filepath.Join(root, "ws")))
			} else {
				require.NoError(t, os.Mkdir(filepath.Join(root, "ws"), 0o750))
				require.NoError(t, os.Symlink(outside, filepath.Join(root, "ws", "ct.git")))
			}
			backend, err := NewGitTreeBackend(root)
			require.NoError(t, err)
			t.Cleanup(func() { require.NoError(t, backend.Close()) })
			_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
			require.ErrorContains(t, err, "unsafe")
		})
	}
}

func TestGitTreeRejectsWritableOwnedDirectories(t *testing.T) {
	root := t.TempDir()
	require.NoError(t, os.Chmod(root, 0o770))
	_, err := NewGitTreeBackend(root)
	require.ErrorContains(t, err, "not group/world writable")
}

func TestGitTreeRejectsRepositoryPathSubstitution(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	repoPath := filepath.Join(root, "ws", "ct.git")
	moved := filepath.Join(root, "ws", "moved.git")
	require.NoError(t, os.Rename(repoPath, moved))
	require.NoError(t, os.Symlink(moved, repoPath))
	_, err = backend.OpenManifest(context.Background(), staged.ManifestRef)
	require.Error(t, err)
}

func TestGitTreeRepositoryReplacementAfterPinDoesNotReceiveWrites(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "seed", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	repoPath := filepath.Join(root, "ws", "ct.git")
	original := filepath.Join(root, "ws", "original.git")
	replacement := filepath.Join(root, "ws", "replacement.git")
	require.NoError(t, copyDirectory(repoPath, replacement))
	backend.afterOpenRepositoryRoot = func() {
		backend.afterOpenRepositoryRoot = func() {}
		require.NoError(t, os.Rename(repoPath, original))
		require.NoError(t, os.Rename(replacement, repoPath))
	}
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("y")}}})
	require.ErrorContains(t, err, "identity changed")
	_, err = os.Stat(filepath.Join(original, "refs", "content-versions", "v1"))
	require.ErrorIs(t, err, os.ErrNotExist)
	_, err = os.Stat(filepath.Join(repoPath, "refs", "content-versions", "v1"))
	require.ErrorIs(t, err, os.ErrNotExist)
}

func TestGitTreePinnedValidatorRejectsReplacementAfterPathValidation(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "seed", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	repoPath := filepath.Join(root, "ws", "ct.git")
	original := filepath.Join(root, "ws", "original.git")
	malicious := filepath.Join(root, "ws", "malicious.git")
	require.NoError(t, copyDirectory(repoPath, malicious))
	config := filepath.Join(malicious, "config")
	file, err := os.OpenFile(config, os.O_APPEND|os.O_WRONLY, 0)
	require.NoError(t, err)
	_, err = file.WriteString("[include]\n\tpath = /tmp/evil\n")
	require.NoError(t, err)
	require.NoError(t, file.Close())
	backend.afterValidateRepositoryPath = func() {
		backend.afterValidateRepositoryPath = func() {}
		require.NoError(t, os.Rename(repoPath, original))
		require.NoError(t, os.Rename(malicious, repoPath))
	}
	_, err = backend.OpenManifest(context.Background(), staged.ManifestRef)
	require.ErrorContains(t, err, "forbidden section")
}

func TestGitTreePinnedValidatorRejectsReplacementOwnerMode(t *testing.T) {
	for _, relative := range []string{".", "objects", "refs", filepath.Join("refs", "content-versions")} {
		t.Run(relative, func(t *testing.T) {
			root := t.TempDir()
			backendValue, err := NewGitTreeBackend(root)
			require.NoError(t, err)
			backend := backendValue.(*gitTreeStorage)
			t.Cleanup(func() { require.NoError(t, backend.Close()) })
			staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "seed", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
			require.NoError(t, err)
			repoPath := filepath.Join(root, "ws", "ct.git")
			original := filepath.Join(root, "ws", "original.git")
			malicious := filepath.Join(root, "ws", "malicious.git")
			require.NoError(t, copyDirectory(repoPath, malicious))
			require.NoError(t, os.Chmod(filepath.Join(malicious, relative), 0o770))
			backend.afterValidateRepositoryPath = func() {
				backend.afterValidateRepositoryPath = func() {}
				require.NoError(t, os.Rename(repoPath, original))
				require.NoError(t, os.Rename(malicious, repoPath))
			}
			_, err = backend.OpenManifest(context.Background(), staged.ManifestRef)
			require.ErrorContains(t, err, "not group/world writable")
		})
	}
}

func TestGitTreePinnedValidatorFailsClosedOnForbiddenSymlink(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "seed", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	repoPath := filepath.Join(root, "ws", "ct.git")
	original := filepath.Join(root, "ws", "original.git")
	malicious := filepath.Join(root, "ws", "malicious.git")
	require.NoError(t, copyDirectory(repoPath, malicious))
	require.NoError(t, os.Symlink("missing", filepath.Join(malicious, "shallow")))
	backend.afterValidateRepositoryPath = func() {
		backend.afterValidateRepositoryPath = func() {}
		require.NoError(t, os.Rename(repoPath, original))
		require.NoError(t, os.Rename(malicious, repoPath))
	}
	_, err = backend.OpenManifest(context.Background(), staged.ManifestRef)
	require.ErrorContains(t, err, "inspect forbidden pinned content git entry")
}

func TestGitTreePinnedValidatorFailsClosedOnForbiddenFIFO(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "seed", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	require.NoError(t, syscall.Mkfifo(filepath.Join(root, "ws", "ct.git", "shallow"), 0o600))
	done := make(chan error, 1)
	go func() {
		_, err := backend.OpenManifest(context.Background(), staged.ManifestRef)
		done <- err
	}()
	select {
	case err := <-done:
		require.Error(t, err)
	case <-time.After(time.Second):
		t.Fatal("pinned validator blocked opening FIFO")
	}
}

func TestGitTreePinnedValidatorRejectsPackedImmutableRef(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "seed", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	repoPath := filepath.Join(root, "ws", "ct.git")
	runGit(t, "--git-dir", repoPath, "pack-refs", "--all")
	_, err = backend.OpenManifest(context.Background(), staged.ManifestRef)
	require.ErrorContains(t, err, "immutable refs must remain loose")
	require.ErrorContains(t, backend.DeletePayload(context.Background(), staged.ManifestRef), "immutable refs must remain loose")
}

func TestGitTreeExactRefFailsAfterImmutableRefTamper(t *testing.T) {
	root := t.TempDir()
	backend, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	first, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	second, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v2", ParentPayloadRef: first.ManifestRef, Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("y")}}})
	require.NoError(t, err)
	_, secondHash, _ := strings.Cut(string(second.ManifestRef), "@")
	repo, err := git.PlainOpen(filepath.Join(root, "ws", "ct.git"))
	require.NoError(t, err)
	require.NoError(t, repo.Storer.SetReference(plumbing.NewHashReference("refs/content-versions/v1", plumbing.NewHash(secondHash))))
	_, err = backend.OpenManifest(context.Background(), first.ManifestRef)
	require.ErrorContains(t, err, "does not match exact commit")
}

func TestGitTreeImportedCommitRejectsSymlinkAndSubmodule(t *testing.T) {
	for _, mode := range []filemode.FileMode{filemode.Symlink, filemode.Submodule} {
		t.Run(mode.String(), func(t *testing.T) {
			root := t.TempDir()
			backend, err := NewGitTreeBackend(root)
			require.NoError(t, err)
			t.Cleanup(func() { require.NoError(t, backend.Close()) })
			staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "seed", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
			require.NoError(t, err)
			repo, err := git.PlainOpen(filepath.Join(root, "ws", "ct.git"))
			require.NoError(t, err)
			_, commitText, _ := strings.Cut(string(staged.ManifestRef), "@")
			commit, err := repo.CommitObject(plumbing.NewHash(commitText))
			require.NoError(t, err)
			tree, err := commit.Tree()
			require.NoError(t, err)
			badTree := object.Tree{Entries: []object.TreeEntry{{Name: "bad", Mode: mode, Hash: tree.Hash}}}
			encodedTree := repo.Storer.NewEncodedObject()
			require.NoError(t, badTree.Encode(encodedTree))
			treeHash, err := repo.Storer.SetEncodedObject(encodedTree)
			require.NoError(t, err)
			badCommit := object.Commit{Author: commit.Author, Committer: commit.Committer, Message: "bad", TreeHash: treeHash}
			encodedCommit := repo.Storer.NewEncodedObject()
			require.NoError(t, badCommit.Encode(encodedCommit))
			badHash, err := repo.Storer.SetEncodedObject(encodedCommit)
			require.NoError(t, err)
			require.NoError(t, repo.Storer.SetReference(plumbing.NewHashReference("refs/content-versions/bad", badHash)))
			badRef := gitPayloadRef("ws", "ct", "bad", badHash)
			_, err = backend.OpenManifest(context.Background(), badRef)
			require.ErrorContains(t, err, "unsupported entry")
			err = backend.Materialize(context.Background(), badRef, filepath.Join(t.TempDir(), "out"), MaterializeLimits{})
			require.ErrorContains(t, err, "unsupported entry")
		})
	}
}

func TestGitTreeRejectsDeepAndEntryHeavyImportedTrees(t *testing.T) {
	for _, test := range []struct {
		name string
		make func(*testing.T, *git.Repository) plumbing.Hash
		want string
	}{
		{name: "depth", want: "depth limit", make: func(t *testing.T, repo *git.Repository) plumbing.Hash {
			encoded := repo.Storer.NewEncodedObject()
			blob := []byte("x")
			encoded.SetType(plumbing.BlobObject)
			encoded.SetSize(1)
			writer, err := encoded.Writer()
			require.NoError(t, err)
			_, err = writer.Write(blob)
			require.NoError(t, err)
			require.NoError(t, writer.Close())
			hash, err := repo.Storer.SetEncodedObject(encoded)
			require.NoError(t, err)
			for depth := 0; depth <= maxGitTreeDepth+1; depth++ {
				tree := object.Tree{Entries: []object.TreeEntry{{Name: "d", Mode: filemode.Dir, Hash: hash}}}
				if depth == 0 {
					tree.Entries[0] = object.TreeEntry{Name: "a", Mode: filemode.Regular, Hash: hash}
				}
				encoded = repo.Storer.NewEncodedObject()
				require.NoError(t, tree.Encode(encoded))
				hash, err = repo.Storer.SetEncodedObject(encoded)
				require.NoError(t, err)
			}
			return hash
		}},
		{name: "entries", want: "entry limit", make: func(t *testing.T, repo *git.Repository) plumbing.Hash {
			empty := object.Tree{}
			encoded := repo.Storer.NewEncodedObject()
			require.NoError(t, empty.Encode(encoded))
			emptyHash, err := repo.Storer.SetEncodedObject(encoded)
			require.NoError(t, err)
			entries := make([]object.TreeEntry, DefaultLimits.MaxFiles*maxGitTreeEntriesPerFile+maxGitTreeDepth+1)
			for index := range entries {
				entries[index] = object.TreeEntry{Name: fmt.Sprintf("d%06d", index), Mode: filemode.Dir, Hash: emptyHash}
			}
			tree := object.Tree{Entries: entries}
			encoded = repo.Storer.NewEncodedObject()
			require.NoError(t, tree.Encode(encoded))
			hash, err := repo.Storer.SetEncodedObject(encoded)
			require.NoError(t, err)
			return hash
		}},
	} {
		t.Run(test.name, func(t *testing.T) {
			root := t.TempDir()
			backend, err := NewGitTreeBackend(root)
			require.NoError(t, err)
			t.Cleanup(func() { require.NoError(t, backend.Close()) })
			seed, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "seed", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
			require.NoError(t, err)
			repo, err := git.PlainOpen(filepath.Join(root, "ws", "ct.git"))
			require.NoError(t, err)
			treeHash := test.make(t, repo)
			_, commitText, _ := strings.Cut(string(seed.ManifestRef), "@")
			parent, err := repo.CommitObject(plumbing.NewHash(commitText))
			require.NoError(t, err)
			commit := object.Commit{Author: parent.Author, Committer: parent.Committer, Message: "bad", TreeHash: treeHash}
			encoded := repo.Storer.NewEncodedObject()
			require.NoError(t, commit.Encode(encoded))
			hash, err := repo.Storer.SetEncodedObject(encoded)
			require.NoError(t, err)
			require.NoError(t, repo.Storer.SetReference(plumbing.NewHashReference("refs/content-versions/bad", hash)))
			_, err = backend.OpenManifest(context.Background(), gitPayloadRef("ws", "ct", "bad", hash))
			require.ErrorContains(t, err, test.want)
		})
	}
}

func TestGitTreeRejectsDeepProductWriteBeforePublishing(t *testing.T) {
	root := t.TempDir()
	backend, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	path := strings.Repeat("d/", maxGitTreeDepth+1) + "a"
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: path, Size: 1, Content: bytes.NewBufferString("x")}}})
	require.ErrorContains(t, err, "depth limit")
	_, statErr := os.Stat(filepath.Join(root, "ws", "ct.git"))
	require.ErrorIs(t, statErr, os.ErrNotExist)
}

func TestGitTreeLineageLockWaitHonorsContext(t *testing.T) {
	backendValue, err := NewGitTreeBackend(t.TempDir())
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	unlock, err := backend.lock(context.Background(), "ws", "ct")
	require.NoError(t, err)
	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
	defer cancel()
	done := make(chan error, 1)
	go func() {
		_, err := backend.lock(ctx, "ws", "ct")
		done <- err
	}()
	require.ErrorIs(t, <-done, context.DeadlineExceeded)
	unlock()
	backend.mu.RLock()
	require.Empty(t, backend.locks)
	backend.mu.RUnlock()
}

func TestGitTreeSyncFailurePreventsImmutableRefPublication(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	backend.syncFile = func(*os.File, string) error { return errors.New("injected sync failure") }
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.ErrorContains(t, err, "sync content git object")
	_, statErr := os.Stat(filepath.Join(root, "ws", "ct.git", "refs", "content-versions", "v1"))
	require.ErrorIs(t, statErr, os.ErrNotExist)
}

func TestGitTreeRefDirectorySyncFailureRemovesImmutableRef(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	seenInitialization := false
	backend.syncFile = func(file *os.File, label string) error {
		if label == "refs/content-versions" {
			if seenInitialization {
				return errors.New("injected ref directory sync failure")
			}
			seenInitialization = true
		}
		return file.Sync()
	}
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.ErrorContains(t, err, "sync immutable content version ref directory")
	_, statErr := os.Stat(filepath.Join(root, "ws", "ct.git", "refs", "content-versions", "v1"))
	require.ErrorIs(t, statErr, os.ErrNotExist)
}

func TestGitTreeInitializationSyncFailuresPreventImmutableRef(t *testing.T) {
	for _, failLabel := range []string{"config", "HEAD", "refs/content-versions", "refs", "repo", "workspace", "root"} {
		t.Run(failLabel, func(t *testing.T) {
			root := t.TempDir()
			backendValue, err := NewGitTreeBackend(root)
			require.NoError(t, err)
			backend := backendValue.(*gitTreeStorage)
			t.Cleanup(func() { require.NoError(t, backend.Close()) })
			backend.syncFile = func(file *os.File, label string) error {
				if label == failLabel {
					return errors.New("injected initialization sync failure")
				}
				return file.Sync()
			}
			_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
			require.ErrorContains(t, err, "sync initialized content git")
			_, statErr := os.Stat(filepath.Join(root, "ws", "ct.git", "refs", "content-versions", "v1"))
			require.ErrorIs(t, statErr, os.ErrNotExist)
			backend.syncFile = syncGitFile
			staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
			require.NoError(t, err)
			require.True(t, staged.Created)
			ready, err := os.ReadFile(filepath.Join(root, "ws", "ct.git", gitRepositoryReadyMarker))
			require.NoError(t, err)
			require.Equal(t, gitRepositoryReadyPayload, ready)
		})
	}
}

func TestGitTreeExistingUnreadyRepositoryReplaysInitializationBarrier(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	backend.syncFile = func(file *os.File, label string) error {
		if label == "root" {
			return errors.New("injected crash before ready marker")
		}
		return file.Sync()
	}
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.Error(t, err)
	backend.syncFile = syncGitFile
	_, err = backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	_, err = os.Stat(filepath.Join(root, "ws", "ct.git", gitRepositoryReadyMarker))
	require.NoError(t, err)
}

func TestGitTreeReadyMarkerCorruptionFailsClosed(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	marker := filepath.Join(root, "ws", "ct.git", gitRepositoryReadyMarker)
	require.NoError(t, os.WriteFile(marker, []byte("wrong-version\n"), 0o640))
	_, err = backend.OpenManifest(context.Background(), staged.ManifestRef)
	require.ErrorContains(t, err, "ready marker is invalid")
}

func TestGitTreeDeletePayloadSyncsRefDirectory(t *testing.T) {
	root := t.TempDir()
	backendValue, err := NewGitTreeBackend(root)
	require.NoError(t, err)
	backend := backendValue.(*gitTreeStorage)
	t.Cleanup(func() { require.NoError(t, backend.Close()) })
	staged, err := backend.StageTree(context.Background(), StageTreeRequest{WorkspaceID: "ws", ContentID: "ct", VersionID: "v1", Files: []File{{Path: "a", Size: 1, Content: bytes.NewBufferString("x")}}})
	require.NoError(t, err)
	syncAttempts := 0
	backend.syncFile = func(file *os.File, label string) error {
		if label == "delete-ref-directory" {
			syncAttempts++
			if syncAttempts == 1 {
				return errors.New("injected delete sync failure")
			}
		}
		return file.Sync()
	}
	err = backend.DeletePayload(context.Background(), staged.ManifestRef)
	require.ErrorContains(t, err, "sync deleted content version ref directory")
	_, err = os.Stat(filepath.Join(root, "ws", "ct.git", "refs", "content-versions", "v1"))
	require.ErrorIs(t, err, os.ErrNotExist)
	require.NoError(t, backend.DeletePayload(context.Background(), staged.ManifestRef))
	require.Equal(t, 2, syncAttempts)
}
