package datastore

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"strings"
	"sync"
	"testing"
	"time"
)

func TestFSLayerIdentifierBoundaries(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()

	for i, size := range []int{49, 50, 51, FSLayerIDMaxBytes} {
		layerID := strings.Repeat(string(rune('a'+i)), size)
		if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: layerID, BaseRootPath: "/repo"}); err != nil {
			t.Fatalf("CreateFSLayer %d bytes: %v", size, err)
		}
		if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
			LayerID:     layerID,
			Path:        "/repo/file.txt",
			Op:          FSLayerEntryOpUpsert,
			Kind:        FSLayerEntryKindFile,
			ContentBlob: []byte("data"),
		}); err != nil {
			t.Fatalf("UpsertFSLayerEntry %d bytes: %v", size, err)
		}
		if err := s.RollbackFSLayer(ctx, layerID); err != nil {
			t.Fatalf("RollbackFSLayer %d bytes: %v", size, err)
		}
		events, err := s.ListFSLayerEvents(ctx, layerID, 0, 10)
		if err != nil {
			t.Fatalf("ListFSLayerEvents %d bytes: %v", size, err)
		}
		if len(events) != 2 ||
			events[0].EventID != compactFSLayerEventID(fmt.Sprintf("%s:1", layerID)) ||
			events[1].EventID != compactFSLayerEventID(fmt.Sprintf("%s:rollback:2", layerID)) {
			t.Fatalf("events for %d-byte layer = %+v", size, events)
		}
		for _, event := range events {
			if len(event.EventID) > fsLayerEventIDMaxBytes {
				t.Fatalf("event_id for %d-byte layer is %d bytes", size, len(event.EventID))
			}
		}
	}
	multibyteLayerID := strings.Repeat("界", 21) + "a"
	if len(multibyteLayerID) != FSLayerIDMaxBytes {
		t.Fatalf("multibyte boundary fixture is %d bytes", len(multibyteLayerID))
	}
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: multibyteLayerID, BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("CreateFSLayer multibyte %d bytes: %v", len(multibyteLayerID), err)
	}

	for _, layerID := range []string{
		strings.Repeat("z", FSLayerIDMaxBytes+1),
		strings.Repeat("界", 22),
		"layer:invalid",
	} {
		err := s.CreateFSLayer(ctx, &FSLayer{LayerID: layerID, BaseRootPath: "/repo"})
		if !errors.Is(err, ErrFSLayerInvalidArgument) {
			t.Fatalf("CreateFSLayer %q err=%v, want ErrFSLayerInvalidArgument", layerID, err)
		}
	}
}

func TestCompactFSLayerEventID(t *testing.T) {
	short := "layer:1"
	if got := compactFSLayerEventID(short); got != short {
		t.Fatalf("compactFSLayerEventID(%q)=%q", short, got)
	}

	long := strings.Repeat("a", FSLayerIDMaxBytes) + ":rollback:1"
	const want = "9463fe5c69f82e1e70c0b0d7cc0d6ca3b5ee8dd25c1d5d97eaf6e940bb6742a5"
	if got := compactFSLayerEventID(long); got != want {
		t.Fatalf("compactFSLayerEventID(long)=%q, want %q", got, want)
	}
	if got := compactFSLayerEventID(long + "b"); got == want {
		t.Fatal("different long event IDs compacted to the same value")
	}
}

func TestFSLayerCreateValidationUsesInvalidArgumentSentinel(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	for _, layer := range []*FSLayer{
		{LayerID: "layer-invalid-state", BaseRootPath: "/repo", State: FSLayerState("invalid")},
		{LayerID: "layer-invalid-durability", BaseRootPath: "/repo", DurabilityMode: FSLayerDurabilityMode("invalid")},
		{LayerID: "layer-invalid-tags", BaseRootPath: "/repo", Tags: map[string]string{"invalid=key": "value"}},
	} {
		if err := s.CreateFSLayer(ctx, layer); !errors.Is(err, ErrFSLayerInvalidArgument) {
			t.Fatalf("CreateFSLayer %s err=%v, want ErrFSLayerInvalidArgument", layer.LayerID, err)
		}
	}
}

func TestFSLayerCheckpointIdentifierBoundaries(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-checkpoint-boundary", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("CreateFSLayer: %v", err)
	}

	checkpointID := strings.Repeat("c", FSLayerCheckpointIDMaxBytes)
	if err := s.CreateFSLayerCheckpoint(ctx, &FSLayerCheckpoint{
		CheckpointID: checkpointID,
		LayerID:      "layer-checkpoint-boundary",
	}); err != nil {
		t.Fatalf("CreateFSLayerCheckpoint %d bytes: %v", FSLayerCheckpointIDMaxBytes, err)
	}
	if got, err := s.GetFSLayerCheckpoint(ctx, checkpointID); err != nil || got.CheckpointID != checkpointID {
		t.Fatalf("GetFSLayerCheckpoint %d bytes got=%+v err=%v", FSLayerCheckpointIDMaxBytes, got, err)
	}

	for _, invalidID := range []string{
		strings.Repeat("c", FSLayerCheckpointIDMaxBytes+1),
		strings.Repeat("界", 22),
	} {
		err := s.CreateFSLayerCheckpoint(ctx, &FSLayerCheckpoint{
			CheckpointID: invalidID,
			LayerID:      "layer-checkpoint-boundary",
		})
		if !errors.Is(err, ErrFSLayerInvalidArgument) {
			t.Fatalf("CreateFSLayerCheckpoint %d-byte id err=%v, want ErrFSLayerInvalidArgument", len(invalidID), err)
		}
		if _, err := s.GetFSLayerCheckpoint(ctx, invalidID); !errors.Is(err, ErrFSLayerInvalidArgument) {
			t.Fatalf("GetFSLayerCheckpoint %d-byte id err=%v, want ErrFSLayerInvalidArgument", len(invalidID), err)
		}
	}
}

func TestNormalizeFSLayerEntryValidatesS3RefOwnership(t *testing.T) {
	for _, storageRef := range []string{
		"blobs/not-a-layer-ref",
		"layers/layer-ref-validation",
		"layers/layer-ref-validation/",
		"layers/layer-ref-validation/nested/object",
		"layers/other-layer/object",
	} {
		entry := &FSLayerEntry{
			LayerID:     "layer-ref-validation",
			Path:        "/file.bin",
			Op:          FSLayerEntryOpUpsert,
			Kind:        FSLayerEntryKindFile,
			StorageType: string(StorageS3),
			StorageRef:  storageRef,
		}
		if err := normalizeFSLayerEntry(entry); err == nil {
			t.Fatalf("normalizeFSLayerEntry accepted storage ref %q", storageRef)
		}
	}
	entry := &FSLayerEntry{
		LayerID:     "layer/with/slashes",
		Path:        "/file.bin",
		Op:          FSLayerEntryOpUpsert,
		Kind:        FSLayerEntryKindFile,
		StorageType: string(StorageS3),
		StorageRef:  "layers/layer/with/slashes/object",
	}
	if err := normalizeFSLayerEntry(entry); err != nil {
		t.Fatalf("normalizeFSLayerEntry rejected owned storage ref: %v", err)
	}
}

func TestFSLayerRoundTripAndState(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	layer := FSLayer{
		LayerID:        "layer-1",
		BaseRootPath:   "/work",
		Name:           "agent task",
		Tags:           map[string]string{"task": "auth", "env": "dev"},
		DurabilityMode: FSLayerDurabilityRestoreSafe,
		ActorID:        "actor-1",
	}
	if err := s.CreateFSLayer(ctx, &layer); err != nil {
		t.Fatalf("CreateFSLayer: %v", err)
	}
	got, err := s.GetFSLayer(ctx, "layer-1")
	if err != nil {
		t.Fatalf("GetFSLayer: %v", err)
	}
	if got.BaseRootPath != "/work/" || got.Name != "agent task" || got.State != FSLayerStateActive || got.ActorID != "actor-1" {
		t.Fatalf("unexpected layer: %+v", got)
	}
	if got.Tags["task"] != "auth" || got.Tags["env"] != "dev" {
		t.Fatalf("unexpected layer tags: %+v", got.Tags)
	}
	byName, err := s.ResolveFSLayerRef(ctx, "agent task")
	if err != nil {
		t.Fatalf("ResolveFSLayerRef name: %v", err)
	}
	if byName.LayerID != "layer-1" {
		t.Fatalf("ResolveFSLayerRef name = %+v, want layer-1", byName)
	}
	byTag, err := s.ResolveFSLayerRef(ctx, "task=auth")
	if err != nil {
		t.Fatalf("ResolveFSLayerRef tag: %v", err)
	}
	if byTag.LayerID != "layer-1" {
		t.Fatalf("ResolveFSLayerRef tag = %+v, want layer-1", byTag)
	}
	byTagKey, err := s.ResolveFSLayerRef(ctx, "tag:env")
	if err != nil {
		t.Fatalf("ResolveFSLayerRef tag key: %v", err)
	}
	if byTagKey.LayerID != "layer-1" {
		t.Fatalf("ResolveFSLayerRef tag key = %+v, want layer-1", byTagKey)
	}
	layers, err := s.ListFSLayers(ctx)
	if err != nil {
		t.Fatalf("ListFSLayers: %v", err)
	}
	if len(layers) != 1 || layers[0].LayerID != "layer-1" {
		t.Fatalf("layers = %+v, want layer-1", layers)
	}
	if err := s.RollbackFSLayer(ctx, "layer-1"); err != nil {
		t.Fatalf("RollbackFSLayer: %v", err)
	}
	rolledBack, err := s.GetFSLayer(ctx, "layer-1")
	if err != nil {
		t.Fatalf("GetFSLayer after rollback: %v", err)
	}
	if rolledBack.State != FSLayerStateAbandoned || rolledBack.SealedAt == nil {
		t.Fatalf("rollback layer = %+v, want abandoned with sealed_at", rolledBack)
	}
}

func TestFSLayerRefAmbiguous(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	for _, id := range []string{"layer-a", "layer-b"} {
		if err := s.CreateFSLayer(ctx, &FSLayer{
			LayerID:      id,
			BaseRootPath: "/work",
			Name:         "shared",
			Tags:         map[string]string{"task": "shared"},
		}); err != nil {
			t.Fatalf("CreateFSLayer %s: %v", id, err)
		}
	}
	if _, err := s.ResolveFSLayerRef(ctx, "shared"); !errors.Is(err, ErrFSLayerRefAmbiguous) {
		t.Fatalf("ResolveFSLayerRef ambiguous name err=%v, want ErrFSLayerRefAmbiguous", err)
	}
	if _, err := s.ResolveFSLayerRef(ctx, "task=shared"); !errors.Is(err, ErrFSLayerRefAmbiguous) {
		t.Fatalf("ResolveFSLayerRef ambiguous tag err=%v, want ErrFSLayerRefAmbiguous", err)
	}
}

func TestFSLayerRollbackRejectsCommittedAndCommitting(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	for _, tt := range []struct {
		id    string
		state FSLayerState
	}{
		{id: "layer-committed", state: FSLayerStateCommitted},
		{id: "layer-committing", state: FSLayerStateCommitting},
	} {
		if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: tt.id, BaseRootPath: "/repo", State: tt.state}); err != nil {
			t.Fatalf("CreateFSLayer %s: %v", tt.id, err)
		}
		if err := s.RollbackFSLayer(ctx, tt.id); !errors.Is(err, ErrFSLayerStateConflict) {
			t.Fatalf("RollbackFSLayer %s err=%v, want ErrFSLayerStateConflict", tt.id, err)
		}
	}
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-conflicted", BaseRootPath: "/repo", State: FSLayerStateConflicted}); err != nil {
		t.Fatalf("CreateFSLayer conflicted: %v", err)
	}
	if err := s.RollbackFSLayer(ctx, "layer-conflicted"); err != nil {
		t.Fatalf("RollbackFSLayer conflicted: %v", err)
	}
}

func TestRollbackFSLayerEmitsEvent(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-rollback-event", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("CreateFSLayer: %v", err)
	}
	// Upsert an entry so there is a prior entry-event seq; the rollback
	// event seq must be strictly greater so the FUSE watcher observes it.
	if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID:     "layer-rollback-event",
		Path:        "/repo/a.txt",
		Op:          FSLayerEntryOpUpsert,
		Kind:        FSLayerEntryKindFile,
		ContentBlob: []byte("data"),
	}); err != nil {
		t.Fatalf("UpsertFSLayerEntry: %v", err)
	}
	priorEvents, err := s.ListFSLayerEvents(ctx, "layer-rollback-event", 0, 1000)
	if err != nil {
		t.Fatalf("ListFSLayerEvents before rollback: %v", err)
	}
	if len(priorEvents) != 1 || priorEvents[0].Op != "upsert" {
		t.Fatalf("prior events = %+v, want 1 upsert event", priorEvents)
	}

	// Rollback → emits a synthetic rollback event.
	if err := s.RollbackFSLayer(ctx, "layer-rollback-event"); err != nil {
		t.Fatalf("RollbackFSLayer: %v", err)
	}
	events, err := s.ListFSLayerEvents(ctx, "layer-rollback-event", 0, 1000)
	if err != nil {
		t.Fatalf("ListFSLayerEvents after rollback: %v", err)
	}
	if len(events) != 2 {
		t.Fatalf("events after rollback = %d, want 2 (upsert + rollback)", len(events))
	}
	rbEvent := events[1]
	if rbEvent.Op != "rollback" {
		t.Fatalf("rollback event op = %q, want %q", rbEvent.Op, "rollback")
	}
	if rbEvent.LayerID != "layer-rollback-event" {
		t.Fatalf("rollback event layer_id = %q, want layer-rollback-event", rbEvent.LayerID)
	}
	if rbEvent.Seq != priorEvents[0].Seq+1 {
		t.Fatalf("rollback event seq %d, want %d (shared namespace, next after upsert)", rbEvent.Seq, priorEvents[0].Seq+1)
	}
	if rbEvent.EventID == "" || rbEvent.EventID == events[0].EventID {
		t.Fatalf("rollback event_id = %q, want unique non-empty", rbEvent.EventID)
	}

	// Idempotent rollback (already abandoned): must NOT re-emit the event.
	if err := s.RollbackFSLayer(ctx, "layer-rollback-event"); err != nil {
		t.Fatalf("RollbackFSLayer idempotent: %v", err)
	}
	events2, err := s.ListFSLayerEvents(ctx, "layer-rollback-event", 0, 1000)
	if err != nil {
		t.Fatalf("ListFSLayerEvents after idempotent rollback: %v", err)
	}
	if len(events2) != len(events) {
		t.Fatalf("events after idempotent rollback = %d, want %d (no re-emit)", len(events2), len(events))
	}
}

func TestRollbackFSLayerNotFound(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.RollbackFSLayer(ctx, "nonexistent-layer"); !errors.Is(err, ErrNotFound) {
		t.Fatalf("RollbackFSLayer nonexistent err=%v, want ErrNotFound", err)
	}
}

func TestFSLayerUpsertRejectsNonActiveLayer(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-committing-upsert", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("CreateFSLayer: %v", err)
	}
	if err := s.BeginFSLayerCommit(ctx, "layer-committing-upsert"); err != nil {
		t.Fatalf("BeginFSLayerCommit: %v", err)
	}
	if err := s.BeginFSLayerCommit(ctx, "layer-committing-upsert"); !errors.Is(err, ErrFSLayerStateConflict) {
		t.Fatalf("BeginFSLayerCommit reentry err=%v, want ErrFSLayerStateConflict", err)
	}
	err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID:     "layer-committing-upsert",
		Path:        "/repo/late.txt",
		Op:          FSLayerEntryOpUpsert,
		Kind:        FSLayerEntryKindFile,
		ContentBlob: []byte("late"),
	})
	if !errors.Is(err, ErrFSLayerStateConflict) {
		t.Fatalf("UpsertFSLayerEntry err=%v, want ErrFSLayerStateConflict", err)
	}
}

func TestFSLayerEntriesAndCheckpoint(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-entries", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("CreateFSLayer: %v", err)
	}
	dir := FSLayerEntry{
		LayerID: "layer-entries",
		Path:    "/repo/tmp",
		Op:      FSLayerEntryOpMkdir,
	}
	if err := s.UpsertFSLayerEntry(ctx, &dir); err != nil {
		t.Fatalf("UpsertFSLayerEntry dir: %v", err)
	}
	link := FSLayerEntry{
		LayerID:     "layer-entries",
		Path:        "/repo/tmp/link",
		Op:          FSLayerEntryOpSymlink,
		ContentText: "/repo/tmp/a.txt",
	}
	if err := s.UpsertFSLayerEntry(ctx, &link); err != nil {
		t.Fatalf("UpsertFSLayerEntry symlink: %v", err)
	}
	file := FSLayerEntry{
		LayerID:     "layer-entries",
		Path:        "/repo/tmp/a.txt",
		Op:          FSLayerEntryOpUpsert,
		Kind:        FSLayerEntryKindFile,
		ContentBlob: []byte("hello"),
		ContentType: "text/plain",
		SizeBytes:   5,
	}
	if err := s.UpsertFSLayerEntry(ctx, &file); err != nil {
		t.Fatalf("UpsertFSLayerEntry file: %v", err)
	}
	gotDir, err := s.GetFSLayerEntry(ctx, "layer-entries", "/repo/tmp/")
	if err != nil {
		t.Fatalf("GetFSLayerEntry dir: %v", err)
	}
	if gotDir.Path != "/repo/tmp/" || gotDir.Kind != FSLayerEntryKindDir || gotDir.Mode != 0o755 {
		t.Fatalf("dir entry = %+v", gotDir)
	}
	gotFile, err := s.GetFSLayerEntry(ctx, "layer-entries", "/repo/tmp/a.txt")
	if err != nil {
		t.Fatalf("GetFSLayerEntry file: %v", err)
	}
	gotLink, err := s.GetFSLayerEntry(ctx, "layer-entries", "/repo/tmp/link")
	if err != nil {
		t.Fatalf("GetFSLayerEntry symlink: %v", err)
	}
	if gotLink.Kind != FSLayerEntryKindSymlink {
		t.Fatalf("symlink entry = %+v, want symlink kind", gotLink)
	}
	if gotFile.EntrySeq != 3 || !bytes.Equal(gotFile.ContentBlob, []byte("hello")) || gotFile.ParentPath != "/repo/tmp/" {
		t.Fatalf("file entry = %+v", gotFile)
	}
	entries, err := s.ListFSLayerEntries(ctx, "layer-entries")
	if err != nil {
		t.Fatalf("ListFSLayerEntries: %v", err)
	}
	if len(entries) != 3 || entries[0].Path != "/repo/tmp/" || entries[1].Path != "/repo/tmp/link" || entries[2].Path != "/repo/tmp/a.txt" {
		t.Fatalf("entries = %+v", entries)
	}
	checkpoint := FSLayerCheckpoint{
		CheckpointID: "ckpt-1",
		LayerID:      "layer-entries",
		Label:        "restore point",
	}
	if err := s.CreateFSLayerCheckpoint(ctx, &checkpoint); err != nil {
		t.Fatalf("CreateFSLayerCheckpoint: %v", err)
	}
	gotCheckpoint, err := s.GetFSLayerCheckpoint(ctx, "ckpt-1")
	if err != nil {
		t.Fatalf("GetFSLayerCheckpoint: %v", err)
	}
	if gotCheckpoint.DurableSeq != 3 || gotCheckpoint.Label != "restore point" {
		t.Fatalf("checkpoint = %+v, want durable_seq=3", gotCheckpoint)
	}
	gotLayer, err := s.GetFSLayer(ctx, "layer-entries")
	if err != nil {
		t.Fatalf("GetFSLayer: %v", err)
	}
	if gotLayer.DurableSeq != 3 {
		t.Fatalf("layer durable_seq=%d, want 3", gotLayer.DurableSeq)
	}
	fileV2 := FSLayerEntry{
		LayerID:     "layer-entries",
		Path:        "/repo/tmp/a.txt",
		Op:          FSLayerEntryOpUpsert,
		Kind:        FSLayerEntryKindFile,
		ContentBlob: []byte("goodbye"),
		ContentType: "text/plain",
		SizeBytes:   7,
	}
	if err := s.UpsertFSLayerEntry(ctx, &fileV2); err != nil {
		t.Fatalf("UpsertFSLayerEntry file v2: %v", err)
	}
	current, err := s.GetFSLayerEntry(ctx, "layer-entries", "/repo/tmp/a.txt")
	if err != nil {
		t.Fatalf("GetFSLayerEntry current: %v", err)
	}
	if current.EntrySeq != 4 || !bytes.Equal(current.ContentBlob, []byte("goodbye")) {
		t.Fatalf("current entry = %+v", current)
	}
	logEntries, err := s.ListFSLayerEntryLog(ctx, "layer-entries")
	if err != nil {
		t.Fatalf("ListFSLayerEntryLog: %v", err)
	}
	if len(logEntries) != 4 || logEntries[0].EntrySeq != 1 || logEntries[3].EntrySeq != 4 {
		t.Fatalf("log entries = %+v, want full ordered log", logEntries)
	}
	atCheckpoint, err := s.GetFSLayerEntryAtSeq(ctx, "layer-entries", "/repo/tmp/a.txt", gotCheckpoint.DurableSeq)
	if err != nil {
		t.Fatalf("GetFSLayerEntryAtSeq: %v", err)
	}
	if atCheckpoint.EntrySeq != 3 || !bytes.Equal(atCheckpoint.ContentBlob, []byte("hello")) {
		t.Fatalf("checkpoint entry = %+v, want seq=3 hello", atCheckpoint)
	}
	checkpointEntries, err := s.ListFSLayerEntriesAtSeq(ctx, "layer-entries", gotCheckpoint.DurableSeq)
	if err != nil {
		t.Fatalf("ListFSLayerEntriesAtSeq: %v", err)
	}
	if len(checkpointEntries) != 3 || checkpointEntries[2].EntrySeq != 3 || !bytes.Equal(checkpointEntries[2].ContentBlob, []byte("hello")) {
		t.Fatalf("checkpoint entries = %+v", checkpointEntries)
	}
	checkpointLog, err := s.ListFSLayerEntryLogAtSeq(ctx, "layer-entries", gotCheckpoint.DurableSeq)
	if err != nil {
		t.Fatalf("ListFSLayerEntryLogAtSeq: %v", err)
	}
	if len(checkpointLog) != 3 || checkpointLog[0].EntrySeq != 1 || checkpointLog[2].EntrySeq != 3 {
		t.Fatalf("checkpoint log = %+v, want ordered entries through checkpoint", checkpointLog)
	}
}

func TestFSLayerEntryMustStayWithinBaseRoot(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-scope", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("CreateFSLayer: %v", err)
	}
	if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID:     "layer-scope",
		Path:        "/other/a.txt",
		Op:          FSLayerEntryOpUpsert,
		ContentBlob: []byte("outside"),
	}); err == nil {
		t.Fatal("UpsertFSLayerEntry outside base root succeeded, want error")
	}
	if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID:     "layer-scope",
		Path:        "/repo/a.txt",
		Op:          FSLayerEntryOpRename,
		ContentText: "/other/a.txt",
	}); err == nil {
		t.Fatal("UpsertFSLayerEntry rename outside base root succeeded, want error")
	}
}

func TestFSLayerCheckpointEmptyLayerUsesZeroDurableSeq(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-empty", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("CreateFSLayer: %v", err)
	}
	checkpoint := FSLayerCheckpoint{
		CheckpointID: "ckpt-empty",
		LayerID:      "layer-empty",
		DurableSeq:   99,
	}
	if err := s.CreateFSLayerCheckpoint(ctx, &checkpoint); err != nil {
		t.Fatalf("CreateFSLayerCheckpoint: %v", err)
	}
	got, err := s.GetFSLayerCheckpoint(ctx, "ckpt-empty")
	if err != nil {
		t.Fatalf("GetFSLayerCheckpoint: %v", err)
	}
	if got.DurableSeq != 0 || checkpoint.DurableSeq != 0 {
		t.Fatalf("checkpoint durable seq got=%d stored=%d, want 0", checkpoint.DurableSeq, got.DurableSeq)
	}
}

func TestFSLayerConcurrentDifferentPathUpserts(t *testing.T) {
	// Unique contiguous event seqs under concurrent different-path upserts.
	// On TiDB this fails if allocation reverts to a snapshot MAX(); on
	// MySQL REPEATABLE READ the read view is created after the layer-lock
	// wait, so the old MAX() path would also pass here.
	runFSLayerConcurrentDifferentPathUpserts(t, newTestStore(t), "layer-concurrent-seq")
}

func runFSLayerConcurrentDifferentPathUpserts(t *testing.T, s *Store, layerID string) {
	t.Helper()
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: layerID, BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("CreateFSLayer: %v", err)
	}
	const n = 20
	errs := make([]error, n)
	var wg sync.WaitGroup
	wg.Add(n)
	for i := 0; i < n; i++ {
		go func(i int) {
			defer wg.Done()
			errs[i] = s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
				LayerID:     layerID,
				Path:        fmt.Sprintf("/repo/f%d.txt", i),
				Op:          FSLayerEntryOpUpsert,
				Kind:        FSLayerEntryKindFile,
				ContentBlob: []byte("x"),
			})
		}(i)
	}
	wg.Wait()
	for i, err := range errs {
		if err != nil {
			t.Fatalf("UpsertFSLayerEntry %d: %v", i, err)
		}
	}
	events, err := s.ListFSLayerEvents(ctx, layerID, 0, 1000)
	if err != nil {
		t.Fatalf("ListFSLayerEvents: %v", err)
	}
	if len(events) != n {
		t.Fatalf("events=%d, want %d: %+v", len(events), n, events)
	}
	seen := make(map[int64]struct{}, n)
	for _, ev := range events {
		if _, ok := seen[ev.Seq]; ok {
			t.Fatalf("duplicate event seq %d", ev.Seq)
		}
		seen[ev.Seq] = struct{}{}
	}
	for seq := int64(1); seq <= n; seq++ {
		if _, ok := seen[seq]; !ok {
			t.Fatalf("missing event seq %d in %+v", seq, events)
		}
	}
}

func TestPublishFSLayerUploadIsAtomicWithCheckpointAndIdempotent(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	now := time.Now().UTC()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-upload", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("create layer: %v", err)
	}
	baseRevision := int64(0)
	upload := &Upload{
		UploadID:         "upload-layer-1",
		FileID:           "object-layer-1",
		TargetKind:       UploadTargetFSLayer,
		LayerID:          "layer-upload",
		TargetPath:       "/repo/large.bin",
		BaseInodeID:      "",
		ExpectedRevision: &baseRevision,
		Mode:             0o640,
		S3UploadID:       "s3-upload-layer-1",
		S3Key:            "layers/layer-upload/object-layer-1",
		TotalSize:        12,
		PartSize:         12,
		PartsTotal:       1,
		Status:           UploadUploading,
		CreatedAt:        now,
		UpdatedAt:        now,
		ExpiresAt:        now.Add(time.Hour),
	}
	if err := s.InsertUpload(ctx, upload); err != nil {
		t.Fatalf("insert upload: %v", err)
	}
	before := &FSLayerCheckpoint{CheckpointID: "before-upload", LayerID: upload.LayerID}
	if err := s.CreateFSLayerCheckpoint(ctx, before); err != nil {
		t.Fatalf("checkpoint before publish: %v", err)
	}
	if before.DurableSeq != 0 {
		t.Fatalf("checkpoint before durable_seq = %d, want 0", before.DurableSeq)
	}

	entry := &FSLayerEntry{
		LayerID:                upload.LayerID,
		Path:                   upload.TargetPath,
		Op:                     FSLayerEntryOpUpsert,
		Kind:                   FSLayerEntryKindFile,
		BaseInodeID:            upload.BaseInodeID,
		BaseRevision:           baseRevision,
		StorageType:            string(StorageS3),
		StorageRef:             upload.S3Key,
		StorageRefHash:         StorageRefHash(upload.S3Key),
		StorageEncryptionMode:  StorageEncryptionNone,
		StorageEncryptionKeyID: "",
		ChecksumSHA256:         strings.Repeat("a", 64),
		SizeBytes:              upload.TotalSize,
		Mode:                   upload.Mode,
	}
	published, err := s.PublishFSLayerUploadResult(ctx, upload.UploadID, entry)
	if err != nil {
		t.Fatalf("publish upload: %v", err)
	}
	if !published {
		t.Fatal("first publish did not report the upload transition")
	}
	if entry.EntrySeq != 1 {
		t.Fatalf("published entry seq = %d, want 1", entry.EntrySeq)
	}
	storedUpload, err := s.GetUpload(ctx, upload.UploadID)
	if err != nil {
		t.Fatalf("get completed upload: %v", err)
	}
	if storedUpload.Status != UploadCompleted {
		t.Fatalf("upload status = %q, want %q", storedUpload.Status, UploadCompleted)
	}

	published, err = s.PublishFSLayerUploadResult(ctx, upload.UploadID, entry)
	if err != nil {
		t.Fatalf("idempotent publish: %v", err)
	}
	if published {
		t.Fatal("idempotent publish reported a second upload transition")
	}
	mismatched := *entry
	mismatched.StorageRef = "layers/layer-upload/different-object"
	mismatched.StorageRefHash = StorageRefHash(mismatched.StorageRef)
	if err := s.PublishFSLayerUpload(ctx, upload.UploadID, &mismatched); !errors.Is(err, ErrFSLayerInvalidArgument) {
		t.Fatalf("mismatched publish error = %v, want ErrFSLayerInvalidArgument", err)
	}
	entries, err := s.ListFSLayerEntryLog(ctx, upload.LayerID)
	if err != nil {
		t.Fatalf("list layer log: %v", err)
	}
	if len(entries) != 1 || entries[0].EntrySeq != 1 || entries[0].StorageRef != upload.S3Key {
		t.Fatalf("entries after retry = %+v", entries)
	}

	after := &FSLayerCheckpoint{CheckpointID: "after-upload", LayerID: upload.LayerID}
	if err := s.CreateFSLayerCheckpoint(ctx, after); err != nil {
		t.Fatalf("checkpoint after publish: %v", err)
	}
	if after.DurableSeq != 1 {
		t.Fatalf("checkpoint after durable_seq = %d, want 1", after.DurableSeq)
	}
}

func TestPublishFSLayerUploadRejectsSealedLayer(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	now := time.Now().UTC()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-sealed-upload", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("create layer: %v", err)
	}
	upload := &Upload{
		UploadID:   "upload-sealed-layer",
		FileID:     "object-sealed-layer",
		TargetKind: UploadTargetFSLayer,
		LayerID:    "layer-sealed-upload",
		TargetPath: "/repo/large.bin",
		Mode:       0o644,
		S3UploadID: "s3-upload-sealed-layer",
		S3Key:      "layers/layer-sealed-upload/object-sealed-layer",
		TotalSize:  12,
		PartSize:   12,
		PartsTotal: 1,
		Status:     UploadUploading,
		CreatedAt:  now,
		UpdatedAt:  now,
		ExpiresAt:  now.Add(time.Hour),
	}
	if err := s.InsertUpload(ctx, upload); err != nil {
		t.Fatalf("insert upload: %v", err)
	}
	if err := s.SetFSLayerState(ctx, upload.LayerID, FSLayerStateSealed); err != nil {
		t.Fatalf("seal layer: %v", err)
	}
	entry := &FSLayerEntry{
		LayerID:     upload.LayerID,
		Path:        upload.TargetPath,
		Op:          FSLayerEntryOpUpsert,
		Kind:        FSLayerEntryKindFile,
		StorageType: string(StorageS3),
		StorageRef:  upload.S3Key,
		SizeBytes:   upload.TotalSize,
		Mode:        upload.Mode,
	}
	if err := s.PublishFSLayerUpload(ctx, upload.UploadID, entry); !errors.Is(err, ErrFSLayerStateConflict) {
		t.Fatalf("publish sealed layer error = %v, want ErrFSLayerStateConflict", err)
	}
	if _, err := s.GetFSLayerEntry(ctx, upload.LayerID, upload.TargetPath); !errors.Is(err, ErrNotFound) {
		t.Fatalf("entry after rejected publish error = %v, want ErrNotFound", err)
	}
}

func TestFSLayerNotFound(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if _, err := s.GetFSLayer(ctx, "missing"); !errors.Is(err, ErrNotFound) {
		t.Fatalf("GetFSLayer missing err=%v, want ErrNotFound", err)
	}
	if err := s.RollbackFSLayer(ctx, "missing"); !errors.Is(err, ErrNotFound) {
		t.Fatalf("RollbackFSLayer missing err=%v, want ErrNotFound", err)
	}
}

func TestFSLayerHasOverlayDrafts(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "drafts-root", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("create: %v", err)
	}
	has, err := s.FSLayerHasOverlayDrafts(ctx, "drafts-root", nil)
	if err != nil {
		t.Fatalf("empty: %v", err)
	}
	if has {
		t.Fatal("empty layer must not report overlay drafts")
	}
	if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID: "drafts-root", Path: "/repo/a.txt", Op: FSLayerEntryOpUpsert, Kind: FSLayerEntryKindFile, ContentBlob: []byte("a"),
	}); err != nil {
		t.Fatalf("upsert: %v", err)
	}
	has, err = s.FSLayerHasOverlayDrafts(ctx, "drafts-root", nil)
	if err != nil || has {
		t.Fatalf("upsert-only has=%v err=%v, want false", has, err)
	}
	if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID: "drafts-root", Path: "/repo/a.txt", Op: FSLayerEntryOpChmod, Kind: FSLayerEntryKindFile, Mode: 0o600,
	}); err != nil {
		t.Fatalf("chmod: %v", err)
	}
	has, err = s.FSLayerHasOverlayDrafts(ctx, "drafts-root", nil)
	if err != nil || !has {
		t.Fatalf("after chmod has=%v err=%v, want true", has, err)
	}
	limit := int64(1)
	has, err = s.FSLayerHasOverlayDrafts(ctx, "drafts-root", &limit)
	if err != nil || has {
		t.Fatalf("max_seq=1 has=%v err=%v, want false (chmod is seq 2)", has, err)
	}
}

func TestFSLayerChainHasRemapDrafts(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "remap-root", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("create root: %v", err)
	}
	has, err := s.FSLayerChainHasRemapDrafts(ctx, "remap-root", nil)
	if err != nil || has {
		t.Fatalf("empty has=%v err=%v, want false", has, err)
	}
	if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID: "remap-root", Path: "/repo/a.txt", Op: FSLayerEntryOpUpsert, Kind: FSLayerEntryKindFile, ContentBlob: []byte("a"),
	}); err != nil {
		t.Fatalf("upsert: %v", err)
	}
	has, err = s.FSLayerChainHasRemapDrafts(ctx, "remap-root", nil)
	if err != nil || has {
		t.Fatalf("upsert-only has=%v err=%v, want false", has, err)
	}
	if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID: "remap-root", Path: "/repo/a.txt", Op: FSLayerEntryOpChmod, Kind: FSLayerEntryKindFile, Mode: 0o600,
	}); err != nil {
		t.Fatalf("chmod: %v", err)
	}
	has, err = s.FSLayerChainHasRemapDrafts(ctx, "remap-root", nil)
	if err != nil || has {
		t.Fatalf("chmod-only has=%v err=%v, want false", has, err)
	}
	child, err := s.ForkFSLayer(ctx, FSLayerForkOptions{ChildLayerID: "remap-child", ParentLayerID: "remap-root"})
	if err != nil {
		t.Fatalf("fork: %v", err)
	}
	has, err = s.FSLayerChainHasRemapDrafts(ctx, child.LayerID, nil)
	if err != nil || has {
		t.Fatalf("fork of upsert/chmod has=%v err=%v, want false", has, err)
	}
	if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID: "remap-root", Path: "/repo/old/", Op: FSLayerEntryOpRename, Kind: FSLayerEntryKindDir, ContentText: "/repo/new/",
	}); err != nil {
		t.Fatalf("parent rename after fork: %v", err)
	}
	has, err = s.FSLayerChainHasRemapDrafts(ctx, child.LayerID, nil)
	if err != nil || has {
		t.Fatalf("child must not see parent rename after origin pin has=%v err=%v", has, err)
	}
	has, err = s.FSLayerChainHasRemapDrafts(ctx, "remap-root", nil)
	if err != nil || !has {
		t.Fatalf("root after rename has=%v err=%v, want true", has, err)
	}
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "remap-src", BaseRootPath: "/repo"}); err != nil {
		t.Fatalf("create src: %v", err)
	}
	if err := s.UpsertFSLayerEntry(ctx, &FSLayerEntry{
		LayerID: "remap-src", Path: "/repo/x/", Op: FSLayerEntryOpRename, Kind: FSLayerEntryKindDir, ContentText: "/repo/y/",
	}); err != nil {
		t.Fatalf("src rename: %v", err)
	}
	pinned, err := s.ForkFSLayer(ctx, FSLayerForkOptions{ChildLayerID: "remap-pinned", ParentLayerID: "remap-src"})
	if err != nil {
		t.Fatalf("fork pinned: %v", err)
	}
	has, err = s.FSLayerChainHasRemapDrafts(ctx, pinned.LayerID, nil)
	if err != nil || !has {
		t.Fatalf("child of rename parent has=%v err=%v, want true", has, err)
	}
}
