package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/gofiber/fiber/v2"

	filemanager "wucher/internal/domain/file_manager"
	"wucher/internal/shared/pkg/uuidv7"
)

type fileManagerFolderHandlerServiceMock struct {
	filemanager.Service

	createFolderFn     func(context.Context, filemanager.CreateFolderParams) (*filemanager.Folder, error)
	moveFolderFn       func(context.Context, []byte, filemanager.MoveFolderParams) (*filemanager.Folder, error)
	moveFileFn         func(context.Context, []byte, filemanager.MoveFileParams) (*filemanager.File, error)
	deleteFolderFn     func(context.Context, []byte, []byte, *time.Time) error
	deleteFileFn       func(context.Context, []byte, []byte, *time.Time) error
	restoreFolderFn    func(context.Context, []byte, []byte, bool, []byte) error
	restoreFileFn      func(context.Context, []byte, []byte, bool, []byte) error
	getFolderFn        func(context.Context, []byte) (*filemanager.Folder, error)
	getFileFn          func(context.Context, []byte) (*filemanager.File, error)
	getRootFn          func(context.Context) (*filemanager.Folder, error)
	purgeFolderFn      func(context.Context, []byte) error
	purgeFileFn        func(context.Context, []byte) error
	listFoldersFn      func(context.Context, []byte, string, int, int) ([]filemanager.Folder, int64, error)
	listNodesFn        func(context.Context, []byte) ([]filemanager.Folder, []filemanager.File, error)
	listTrashFoldersFn func(context.Context, []byte, string, int, int) ([]filemanager.Folder, int64, error)
	listTrashNodesFn   func(context.Context) ([]filemanager.Folder, []filemanager.File, error)
}

func (m *fileManagerFolderHandlerServiceMock) CreateFolderNode(ctx context.Context, params filemanager.CreateFolderParams) (*filemanager.Folder, error) {
	if m.createFolderFn != nil {
		return m.createFolderFn(ctx, params)
	}
	return nil, nil
}

func (m *fileManagerFolderHandlerServiceMock) MoveFolderNode(ctx context.Context, id []byte, params filemanager.MoveFolderParams) (*filemanager.Folder, error) {
	if m.moveFolderFn != nil {
		return m.moveFolderFn(ctx, id, params)
	}
	return nil, filemanager.ErrFolderNotFound
}

func (m *fileManagerFolderHandlerServiceMock) MoveFileNode(ctx context.Context, id []byte, params filemanager.MoveFileParams) (*filemanager.File, error) {
	if m.moveFileFn != nil {
		return m.moveFileFn(ctx, id, params)
	}
	return nil, filemanager.ErrFileNotFound
}

func (m *fileManagerFolderHandlerServiceMock) DeleteFolder(ctx context.Context, id []byte, deletedBy []byte, purgeAt *time.Time) error {
	if m.deleteFolderFn != nil {
		return m.deleteFolderFn(ctx, id, deletedBy, purgeAt)
	}
	return filemanager.ErrFolderNotFound
}

func (m *fileManagerFolderHandlerServiceMock) DeleteFile(ctx context.Context, id []byte, deletedBy []byte, purgeAt *time.Time) error {
	if m.deleteFileFn != nil {
		return m.deleteFileFn(ctx, id, deletedBy, purgeAt)
	}
	return filemanager.ErrFileNotFound
}

func (m *fileManagerFolderHandlerServiceMock) RestoreFolder(ctx context.Context, id []byte, targetParentID []byte, targetProvided bool, restoredBy []byte) error {
	if m.restoreFolderFn != nil {
		return m.restoreFolderFn(ctx, id, targetParentID, targetProvided, restoredBy)
	}
	return filemanager.ErrFolderNotFound
}

func (m *fileManagerFolderHandlerServiceMock) RestoreFile(ctx context.Context, id []byte, targetFolderID []byte, targetProvided bool, restoredBy []byte) error {
	if m.restoreFileFn != nil {
		return m.restoreFileFn(ctx, id, targetFolderID, targetProvided, restoredBy)
	}
	return filemanager.ErrFileNotFound
}

func (m *fileManagerFolderHandlerServiceMock) GetFolderByID(ctx context.Context, id []byte) (*filemanager.Folder, error) {
	if m.getFolderFn != nil {
		return m.getFolderFn(ctx, id)
	}
	return nil, nil
}

func (m *fileManagerFolderHandlerServiceMock) GetFileByID(ctx context.Context, id []byte) (*filemanager.File, error) {
	if m.getFileFn != nil {
		return m.getFileFn(ctx, id)
	}
	return nil, nil
}

func (m *fileManagerFolderHandlerServiceMock) GetOrCreateSystemRootFolder(ctx context.Context) (*filemanager.Folder, error) {
	if m.getRootFn != nil {
		return m.getRootFn(ctx)
	}
	return &filemanager.Folder{ID: uuidv7.MustBytes()}, nil
}

func (m *fileManagerFolderHandlerServiceMock) PurgeFolderNode(ctx context.Context, id []byte) error {
	if m.purgeFolderFn != nil {
		return m.purgeFolderFn(ctx, id)
	}
	return filemanager.ErrFolderNotFound
}

func (m *fileManagerFolderHandlerServiceMock) PurgeFileNode(ctx context.Context, id []byte) error {
	if m.purgeFileFn != nil {
		return m.purgeFileFn(ctx, id)
	}
	return filemanager.ErrFileNotFound
}

func (m *fileManagerFolderHandlerServiceMock) ListFoldersByParent(ctx context.Context, parentID []byte, sort string, limit, offset int) ([]filemanager.Folder, int64, error) {
	if m.listFoldersFn != nil {
		return m.listFoldersFn(ctx, parentID, sort, limit, offset)
	}
	return nil, 0, nil
}

func (m *fileManagerFolderHandlerServiceMock) ListNodesByParent(ctx context.Context, parentID []byte) ([]filemanager.Folder, []filemanager.File, error) {
	if m.listNodesFn != nil {
		return m.listNodesFn(ctx, parentID)
	}
	return nil, nil, nil
}

func (m *fileManagerFolderHandlerServiceMock) ListTrashFoldersByParent(ctx context.Context, parentID []byte, sort string, limit, offset int) ([]filemanager.Folder, int64, error) {
	if m.listTrashFoldersFn != nil {
		return m.listTrashFoldersFn(ctx, parentID, sort, limit, offset)
	}
	return nil, 0, nil
}

func (m *fileManagerFolderHandlerServiceMock) ListTrashNodes(ctx context.Context) ([]filemanager.Folder, []filemanager.File, error) {
	if m.listTrashNodesFn != nil {
		return m.listTrashNodesFn(ctx)
	}
	return nil, nil, nil
}

func newFileManagerFolderHandlerMoveTestApp(h *FileManagerFolderHandler, actor []byte) *fiber.App {
	app := fiber.New()
	app.Use(func(c *fiber.Ctx) error {
		if len(actor) > 0 {
			c.Locals("user_id", append([]byte(nil), actor...))
		}
		return c.Next()
	})
	app.Patch("/api/v1/file-manager/move", h.MoveNodes)
	app.Delete("/api/v1/file-manager/delete", h.DeleteNodesBulk)
	app.Patch("/api/v1/file-manager/restore", h.RestoreNodesBulk)
	app.Delete("/api/v1/file-manager/purge-delete", h.PurgeNodesBulk)
	app.Post("/api/v1/file-manager/folders/create", h.CreateFolder)
	app.Get("/api/v1/file-manager/folders/get/:uuid", h.GetFolder)
	app.Get("/api/v1/file-manager/folders/get-all", h.ListFolders)
	app.Get("/api/v1/file-manager/get-all", h.ListAll)
	app.Get("/api/v1/file-manager/folders/trash/get-all", h.ListTrashFolders)
	app.Get("/api/v1/file-manager/trash/get-all", h.ListTrashItems)
	app.Patch("/api/v1/file-manager/folders/update/:uuid", h.UpdateFolder)
	app.Delete("/api/v1/file-manager/folders/delete/:uuid", h.DeleteFolder)
	app.Patch("/api/v1/file-manager/folders/restore/:uuid", h.RestoreFolder)
	return app
}

func TestReplacePathPrefix(t *testing.T) {
	tests := []struct {
		name      string
		pathCache string
		oldPrefix string
		newPrefix string
		want      string
	}{
		{name: "same path", pathCache: "/root/a", oldPrefix: "/root/a", newPrefix: "/x/a", want: "/x/a"},
		{name: "descendant path", pathCache: "/root/a/b/c", oldPrefix: "/root/a", newPrefix: "/x/a", want: "/x/a/b/c"},
		{name: "unrelated path", pathCache: "/other/a", oldPrefix: "/root/a", newPrefix: "/x/a", want: "/other/a"},
		{name: "empty old prefix", pathCache: "/root/a", oldPrefix: "", newPrefix: "/x/a", want: "/root/a"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got := replacePathPrefix(tc.pathCache, tc.oldPrefix, tc.newPrefix); got != tc.want {
				t.Fatalf("replacePathPrefix() = %q, want %q", got, tc.want)
			}
		})
	}
}

func TestParentPathFromCache(t *testing.T) {
	tests := []struct {
		name string
		path string
		want string
	}{
		{name: "root node", path: "/root", want: ""},
		{name: "nested node", path: "/root/ops", want: "/root"},
		{name: "deep nested", path: "/root/ops/dev", want: "/root/ops"},
		{name: "with trailing slash", path: "/root/ops/", want: "/root"},
		{name: "empty", path: "", want: ""},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got := parentPathFromCache(tc.path); got != tc.want {
				t.Fatalf("parentPathFromCache() = %q, want %q", got, tc.want)
			}
		})
	}
}

func TestParseUploadNameOverrides(t *testing.T) {
	tests := []struct {
		name       string
		formValues map[string][]string
		filesCount int
		wantLen    int
		wantErr    bool
	}{
		{
			name:       "use names",
			formValues: map[string][]string{"names": {"a.pdf", "b.pdf"}},
			filesCount: 2,
			wantLen:    2,
		},
		{
			name:       "fallback to name",
			formValues: map[string][]string{"name": {"a.pdf"}},
			filesCount: 1,
			wantLen:    1,
		},
		{
			name:       "mismatch count",
			formValues: map[string][]string{"names": {"a.pdf"}},
			filesCount: 2,
			wantErr:    true,
		},
		{
			name:       "empty override allowed",
			formValues: map[string][]string{},
			filesCount: 3,
			wantLen:    0,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, err := parseUploadNameOverrides(tc.formValues, tc.filesCount)
			if tc.wantErr {
				if err == nil {
					t.Fatalf("expected error, got nil")
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if len(got) != tc.wantLen {
				t.Fatalf("len(overrides) = %d, want %d", len(got), tc.wantLen)
			}
		})
	}
}

func TestIsFileManagerCreateBulkPayload(t *testing.T) {
	tests := []struct {
		name    string
		body    string
		want    bool
		wantErr bool
	}{
		{name: "single object", body: `{"data":{"type":"file_manager_file_create","attributes":{"upload_file_uuid":"x"}}}`, want: false},
		{name: "bulk array", body: `{"data":[{"type":"file_manager_file_create","attributes":{"upload_file_uuid":"x"}}]}`, want: true},
		{name: "empty data", body: `{"data":null}`, want: false},
		{name: "invalid json", body: `{`, wantErr: true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, err := isFileManagerCreateBulkPayload([]byte(tc.body))
			if tc.wantErr {
				if err == nil {
					t.Fatalf("expected error, got nil")
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if got != tc.want {
				t.Fatalf("isFileManagerCreateBulkPayload() = %v, want %v", got, tc.want)
			}
		})
	}
}

func TestFileManagerFolderHandlerMoveNodes(t *testing.T) {
	t.Run("moves folder and file in one bulk request", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		folderID := uuidv7.MustBytes()
		fileID := uuidv7.MustBytes()
		destinationID := uuidv7.MustBytes()
		destinationUUID := mustUUIDStringFileManager(t, destinationID)

		svc := &fileManagerFolderHandlerServiceMock{
			moveFolderFn: func(_ context.Context, id []byte, _ filemanager.MoveFolderParams) (*filemanager.Folder, error) {
				if bytes.Equal(id, folderID) {
					return &filemanager.Folder{ID: folderID}, nil
				}
				return nil, filemanager.ErrFolderNotFound
			},
			moveFileFn: func(_ context.Context, id []byte, _ filemanager.MoveFileParams) (*filemanager.File, error) {
				if bytes.Equal(id, fileID) {
					return &filemanager.File{ID: fileID}, nil
				}
				return nil, filemanager.ErrFileNotFound
			},
		}

		h := NewFileManagerFolderHandler(svc, nil)
		app := newFileManagerFolderHandlerMoveTestApp(h, actorID)

		body := []byte(`{"data":[{"uuid":"` + mustUUIDStringFileManager(t, folderID) + `","destination_folder_uuid":"` + destinationUUID + `"},{"uuid":"` + mustUUIDStringFileManager(t, fileID) + `","destination_folder_uuid":null}]}`)
		req, _ := http.NewRequest(http.MethodPatch, "/api/v1/file-manager/move", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		resp, err := app.Test(req)
		if err != nil {
			t.Fatalf("app.Test: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}

		var out map[string]any
		if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
			t.Fatalf("decode response: %v", err)
		}
		data, ok := out["data"].([]any)
		if !ok || len(data) != 2 {
			t.Fatalf("expected 2 data items, got %#v", out["data"])
		}

		first, _ := data[0].(map[string]any)
		if success, _ := first["success"].(bool); !success {
			t.Fatalf("first item should succeed: %#v", first)
		}
		firstRes, _ := first["resource"].(map[string]any)
		firstAttrs, _ := firstRes["attributes"].(map[string]any)
		if firstAttrs["node_type"] != "folder" {
			t.Fatalf("first node_type should be folder, got %#v", firstAttrs["node_type"])
		}
		firstLoc, _ := firstAttrs["location"].(map[string]any)
		if firstLoc["folder_id"] != destinationUUID {
			t.Fatalf("first folder_id mismatch: %#v", firstLoc["folder_id"])
		}

		second, _ := data[1].(map[string]any)
		if success, _ := second["success"].(bool); !success {
			t.Fatalf("second item should succeed: %#v", second)
		}
		secondRes, _ := second["resource"].(map[string]any)
		secondAttrs, _ := secondRes["attributes"].(map[string]any)
		if secondAttrs["node_type"] != "file" {
			t.Fatalf("second node_type should be file, got %#v", secondAttrs["node_type"])
		}
		if _, exists := secondAttrs["location"]; exists {
			t.Fatalf("location should be omitted for root move")
		}
	})

	t.Run("returns item error for invalid uuid", func(t *testing.T) {
		h := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{}, nil)
		app := newFileManagerFolderHandlerMoveTestApp(h, nil)

		body := []byte(`{"data":[{"uuid":"invalid-uuid","destination_folder_uuid":null}]}`)
		req, _ := http.NewRequest(http.MethodPatch, "/api/v1/file-manager/move", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		resp, err := app.Test(req)
		if err != nil {
			t.Fatalf("app.Test: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}

		var out map[string]any
		if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
			t.Fatalf("decode response: %v", err)
		}
		data, _ := out["data"].([]any)
		item, _ := data[0].(map[string]any)
		if success, _ := item["success"].(bool); success {
			t.Fatalf("item should fail for invalid uuid")
		}
		errObj, _ := item["error"].(map[string]any)
		if errObj["status"] != float64(422) {
			t.Fatalf("status should be 422, got %#v", errObj["status"])
		}
	})

	t.Run("treats destination null as system root for file move", func(t *testing.T) {
		fileID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		var capturedFolderID []byte
		var capturedFolderProvided bool

		svc := &fileManagerFolderHandlerServiceMock{
			moveFolderFn: func(_ context.Context, _ []byte, _ filemanager.MoveFolderParams) (*filemanager.Folder, error) {
				return nil, filemanager.ErrFolderNotFound
			},
			moveFileFn: func(_ context.Context, id []byte, params filemanager.MoveFileParams) (*filemanager.File, error) {
				if !bytes.Equal(id, fileID) {
					return nil, filemanager.ErrFileNotFound
				}
				capturedFolderID = append([]byte(nil), params.FolderID...)
				capturedFolderProvided = params.FolderProvided
				return &filemanager.File{ID: fileID}, nil
			},
			getRootFn: func(_ context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID}, nil
			},
		}

		h := NewFileManagerFolderHandler(svc, nil)
		app := newFileManagerFolderHandlerMoveTestApp(h, nil)

		body := []byte(`{"data":[{"uuid":"` + mustUUIDStringFileManager(t, fileID) + `","destination_folder_uuid":null}]}`)
		req, _ := http.NewRequest(http.MethodPatch, "/api/v1/file-manager/move", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		resp, err := app.Test(req)
		if err != nil {
			t.Fatalf("app.Test: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}

		if !capturedFolderProvided {
			t.Fatalf("expected folder move to be provided")
		}
		if !bytes.Equal(capturedFolderID, rootID) {
			t.Fatalf("expected root folder id %q, got %q", mustUUIDStringFileManager(t, rootID), mustUUIDStringFileManager(t, capturedFolderID))
		}
	})
}

func TestFileManagerFolderHandlerPurgeNodesBulk(t *testing.T) {
	t.Run("purges folder and file in one bulk request", func(t *testing.T) {
		folderID := uuidv7.MustBytes()
		fileID := uuidv7.MustBytes()

		svc := &fileManagerFolderHandlerServiceMock{
			purgeFolderFn: func(_ context.Context, id []byte) error {
				if bytes.Equal(id, folderID) {
					return nil
				}
				return filemanager.ErrFolderNotFound
			},
			purgeFileFn: func(_ context.Context, id []byte) error {
				if bytes.Equal(id, fileID) {
					return nil
				}
				return filemanager.ErrFileNotFound
			},
		}

		h := NewFileManagerFolderHandler(svc, nil)
		app := newFileManagerFolderHandlerMoveTestApp(h, nil)

		body := []byte(`{"data":[{"uuid":"` + mustUUIDStringFileManager(t, folderID) + `"},{"uuid":"` + mustUUIDStringFileManager(t, fileID) + `"}]}`)
		req, _ := http.NewRequest(http.MethodDelete, "/api/v1/file-manager/purge-delete", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		resp, err := app.Test(req)
		if err != nil {
			t.Fatalf("app.Test: %v", err)
		}
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}

		var out map[string]any
		if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
			t.Fatalf("decode response: %v", err)
		}
		data, _ := out["data"].([]any)
		if len(data) != 2 {
			t.Fatalf("expected 2 items, got %d", len(data))
		}

		first, _ := data[0].(map[string]any)
		firstRes, _ := first["resource"].(map[string]any)
		firstAttrs, _ := firstRes["attributes"].(map[string]any)
		if firstAttrs["node_type"] != "folder" {
			t.Fatalf("expected first node_type folder, got %#v", firstAttrs["node_type"])
		}

		second, _ := data[1].(map[string]any)
		secondRes, _ := second["resource"].(map[string]any)
		secondAttrs, _ := secondRes["attributes"].(map[string]any)
		if secondAttrs["node_type"] != "file" {
			t.Fatalf("expected second node_type file, got %#v", secondAttrs["node_type"])
		}
	})
}

func TestFileManagerFolderHandlerDeleteNodesBulk(t *testing.T) {
	folderID := uuidv7.MustBytes()
	fileID := uuidv7.MustBytes()
	svc := &fileManagerFolderHandlerServiceMock{
		deleteFolderFn: func(_ context.Context, id []byte, _ []byte, _ *time.Time) error {
			if bytes.Equal(id, folderID) {
				return nil
			}
			return filemanager.ErrFolderNotFound
		},
		deleteFileFn: func(_ context.Context, id []byte, _ []byte, _ *time.Time) error {
			if bytes.Equal(id, fileID) {
				return nil
			}
			return filemanager.ErrFileNotFound
		},
	}
	h := NewFileManagerFolderHandler(svc, nil)
	app := newFileManagerFolderHandlerMoveTestApp(h, uuidv7.MustBytes())
	body := []byte(`{"data":[{"uuid":"` + mustUUIDStringFileManager(t, folderID) + `"},{"uuid":"` + mustUUIDStringFileManager(t, fileID) + `"}]}`)
	req, _ := http.NewRequest(http.MethodDelete, "/api/v1/file-manager/delete", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
}

func TestFileManagerFolderHandlerRestoreNodesBulk(t *testing.T) {
	folderID := uuidv7.MustBytes()
	fileID := uuidv7.MustBytes()
	parentID := uuidv7.MustBytes()
	rootID := uuidv7.MustBytes()
	svc := &fileManagerFolderHandlerServiceMock{
		restoreFolderFn: func(_ context.Context, id []byte, _ []byte, _ bool, _ []byte) error {
			if bytes.Equal(id, folderID) {
				return nil
			}
			return filemanager.ErrFolderNotFound
		},
		restoreFileFn: func(_ context.Context, id []byte, _ []byte, _ bool, _ []byte) error {
			if bytes.Equal(id, fileID) {
				return nil
			}
			return filemanager.ErrFileNotFound
		},
		getFolderFn: func(_ context.Context, id []byte) (*filemanager.Folder, error) {
			if bytes.Equal(id, folderID) {
				return &filemanager.Folder{ID: folderID, ParentID: parentID}, nil
			}
			return nil, nil
		},
		getFileFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
			if bytes.Equal(id, fileID) {
				return &filemanager.File{ID: fileID, FolderID: rootID}, nil
			}
			return nil, nil
		},
		getRootFn: func(_ context.Context) (*filemanager.Folder, error) {
			return &filemanager.Folder{ID: rootID}, nil
		},
	}
	h := NewFileManagerFolderHandler(svc, nil)
	app := newFileManagerFolderHandlerMoveTestApp(h, uuidv7.MustBytes())
	body := []byte(`{"data":[{"uuid":"` + mustUUIDStringFileManager(t, folderID) + `"},{"uuid":"` + mustUUIDStringFileManager(t, fileID) + `"}]}`)
	req, _ := http.NewRequest(http.MethodPatch, "/api/v1/file-manager/restore", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
}

func TestFileManagerFolderHandlerReadAndHelperCoverage(t *testing.T) {
	t.Run("create get list update delete restore", func(t *testing.T) {
		folderID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		now := time.Now().UTC()
		svc := &fileManagerFolderHandlerServiceMock{
			createFolderFn: func(_ context.Context, params filemanager.CreateFolderParams) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: folderID, ParentID: params.ParentID, Name: params.Name, NameNormalized: strings.ToLower(params.Name), CreatedAt: now, UpdatedAt: now}, nil
			},
			getFolderFn: func(_ context.Context, id []byte) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: id, ParentID: rootID, Name: "Ops", NameNormalized: "ops", Depth: 1, CreatedAt: now, UpdatedAt: now}, nil
			},
			listFoldersFn: func(_ context.Context, _ []byte, _ string, _ int, _ int) ([]filemanager.Folder, int64, error) {
				return []filemanager.Folder{{ID: folderID, ParentID: rootID, Name: "Ops", NameNormalized: "ops", CreatedAt: now, UpdatedAt: now}}, 1, nil
			},
			listNodesFn: func(_ context.Context, _ []byte) ([]filemanager.Folder, []filemanager.File, error) {
				return []filemanager.Folder{{ID: folderID, ParentID: rootID, Name: "Ops", PathCache: "/Ops", CreatedAt: now, UpdatedAt: now}},
					[]filemanager.File{{ID: uuidv7.MustBytes(), FolderID: rootID, Name: "a.pdf", ObjectKey: "files/a.pdf", MimeType: "application/pdf", Status: filemanager.FileStatusReady, CreatedAt: now, UpdatedAt: now}}, nil
			},
			listTrashFoldersFn: func(_ context.Context, _ []byte, _ string, _ int, _ int) ([]filemanager.Folder, int64, error) {
				deletedAt := now.Add(-time.Hour)
				return []filemanager.Folder{{ID: folderID, ParentID: rootID, Name: "Trash", NameNormalized: "trash", CreatedAt: now, UpdatedAt: now, DeletedAt: &deletedAt}}, 1, nil
			},
			listTrashNodesFn: func(_ context.Context) ([]filemanager.Folder, []filemanager.File, error) {
				deletedAt := now.Add(-time.Hour)
				return []filemanager.Folder{{ID: folderID, ParentID: rootID, Name: "Trash", PathCache: "/Trash", CreatedAt: now, UpdatedAt: now, DeletedAt: &deletedAt}},
					[]filemanager.File{{ID: uuidv7.MustBytes(), FolderID: rootID, Name: "gone.pdf", ObjectKey: "files/gone.pdf", MimeType: "application/pdf", SizeBytes: 10, CreatedAt: now, UpdatedAt: now, DeletedAt: &deletedAt}}, nil
			},
			moveFolderFn: func(_ context.Context, id []byte, _ filemanager.MoveFolderParams) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: id, ParentID: rootID, Name: "Renamed", NameNormalized: "renamed", CreatedAt: now, UpdatedAt: now}, nil
			},
			deleteFolderFn:  func(context.Context, []byte, []byte, *time.Time) error { return nil },
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getRootFn:       func(context.Context) (*filemanager.Folder, error) { return &filemanager.Folder{ID: rootID}, nil },
		}
		h := NewFileManagerFolderHandler(svc, &fileManagerDownloadURLStorageMock{presignedURL: "https://signed/file.pdf"})
		app := newFileManagerFolderHandlerMoveTestApp(h, uuidv7.MustBytes())

		createBody := []byte(`{"data":{"type":"file_manager_folder","attributes":{"name":"Ops"}}}`)
		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/folders/create", createBody)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201 create, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/folders/get/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 get, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/folders/get-all", nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 list folders, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/get-all", nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 list all, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/folders/trash/get-all", nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 list trash folders, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/trash/get-all?sort=name", nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 list trash items, got %d", resp.StatusCode)
		}

		updateBody := []byte(`{"data":{"type":"file_manager_folder","id":"` + mustUUIDStringFileManager(t, folderID) + `","attributes":{"name":"Renamed"}}}`)
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/update/"+mustUUIDStringFileManager(t, folderID), updateBody)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 update, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/folders/delete/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 delete folder, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/restore/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 restore folder, got %d", resp.StatusCode)
		}
	})

	t.Run("helper resources and sorting", func(t *testing.T) {
		now := time.Now().UTC()
		deletedAt := now.Add(-time.Hour)
		folder := &filemanager.Folder{ID: uuidv7.MustBytes(), ParentID: uuidv7.MustBytes(), Name: "Ops", NameNormalized: "ops", PathCache: "/Ops", Depth: 1, CreatedAt: now, UpdatedAt: now, DeletedAt: &deletedAt}
		file := &filemanager.File{ID: uuidv7.MustBytes(), FolderID: folder.ID, Name: "a.pdf", Extension: "pdf", MimeType: "application/pdf", SizeBytes: 10, Status: filemanager.FileStatusReady, CreatedAt: now, UpdatedAt: now}
		if fileManagerFolderResource(folder).Type != "file_manager_folder" {
			t.Fatalf("unexpected folder resource type")
		}
		if fileManagerFolderDetailResource(folder).Attributes.Location == nil {
			t.Fatalf("expected folder detail location")
		}
		if fileManagerNodeFromFolder(folder, false).Attributes.ParentID == nil {
			t.Fatalf("expected node parent id")
		}
		if fileManagerNodeFromFile(file, true, "https://signed").Attributes.ParentID != nil {
			t.Fatalf("expected nil parent id at root")
		}
		parentID := fileManagerNodeParentID(folder.ID, false)
		if parentID == nil || *parentID == "" {
			t.Fatalf("expected non-empty parent id")
		}

		items := []fileManagerTrashItemView{
			{Name: "b", NodeType: "file", SizeBytes: 1, DeletedAt: now},
			{Name: "a", NodeType: "folder", SizeBytes: 2, DeletedAt: deletedAt},
		}
		sortFileManagerTrashItems(items, "name")
		if items[0].Name != "a" {
			t.Fatalf("expected sorted by name")
		}
		sortFileManagerTrashItems(items, "-type")
		sortFileManagerTrashItems(items, "size_bytes")
		sortFileManagerTrashItems(items, "")
	})

	t.Run("breadcrumb parse uuid and pagination", func(t *testing.T) {
		now := time.Now().UTC()
		rootID := uuidv7.MustBytes()
		childID := uuidv7.MustBytes()
		svc := &fileManagerFolderHandlerServiceMock{
			getFolderFn: func(_ context.Context, id []byte) (*filemanager.Folder, error) {
				if bytes.Equal(id, childID) {
					return &filemanager.Folder{ID: childID, ParentID: rootID, Name: "Child", CreatedAt: now, UpdatedAt: now}, nil
				}
				if bytes.Equal(id, rootID) {
					return &filemanager.Folder{ID: rootID, Name: "Root", CreatedAt: now, UpdatedAt: now}, nil
				}
				return nil, nil
			},
		}
		h := NewFileManagerFolderHandler(svc, nil)
		app := fiber.New()
		app.Get("/:uuid", func(c *fiber.Ctx) error {
			if _, err := h.parseUUIDParam(c, "uuid", "/path/uuid"); err != nil {
				return err
			}
			page, size, offset := parseFileManagerFolderPagination(c)
			if page != 1 || size != 100 || offset != 0 {
				t.Fatalf("unexpected pagination %d %d %d", page, size, offset)
			}
			breadcrumb, err := h.buildExplorerBreadcrumb(c.Context(), childID)
			if err != nil || len(breadcrumb) != 2 {
				t.Fatalf("unexpected breadcrumb len=%d err=%v", len(breadcrumb), err)
			}
			if replacePathPrefix("/root/a", "/root", "/new") != "/new/a" {
				t.Fatalf("unexpected replacePathPrefix")
			}
			if parentPathFromCache("/root/a/b") != "/root/a" {
				t.Fatalf("unexpected parentPathFromCache")
			}
			return nil
		})
		req, _ := http.NewRequest(http.MethodGet, "/bad?page[number]=0&page[size]=999", nil)
		resp, _ := app.Test(req)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})
}

func TestFileManagerFolderHandlerValidationAndErrorCoverage(t *testing.T) {
	t.Run("folder endpoint validation and action errors", func(t *testing.T) {
		folderID := uuidv7.MustBytes()
		parentID := uuidv7.MustBytes()
		svc := &fileManagerFolderHandlerServiceMock{
			createFolderFn: func(context.Context, filemanager.CreateFolderParams) (*filemanager.Folder, error) {
				return nil, filemanager.ErrDuplicateName
			},
			getFolderFn: func(context.Context, []byte) (*filemanager.Folder, error) {
				return nil, errors.New("get failure")
			},
			listFoldersFn: func(context.Context, []byte, string, int, int) ([]filemanager.Folder, int64, error) {
				return nil, 0, errors.New("list folders failure")
			},
			listNodesFn: func(context.Context, []byte) ([]filemanager.Folder, []filemanager.File, error) {
				return nil, nil, errors.New("list nodes failure")
			},
			listTrashFoldersFn: func(context.Context, []byte, string, int, int) ([]filemanager.Folder, int64, error) {
				return nil, 0, errors.New("list trash folders failure")
			},
			moveFolderFn: func(context.Context, []byte, filemanager.MoveFolderParams) (*filemanager.Folder, error) {
				return nil, filemanager.ErrFolderNotFound
			},
			deleteFolderFn: func(context.Context, []byte, []byte, *time.Time) error {
				return filemanager.ErrFolderNotFound
			},
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error {
				return filemanager.ErrFolderNotFound
			},
		}
		h := NewFileManagerFolderHandler(svc, nil)
		app := newFileManagerFolderHandlerMoveTestApp(h, uuidv7.MustBytes())

		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/folders/create", []byte(`{`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 invalid create folder json, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/folders/create", []byte(`{"data":{"type":"file_manager_folder","attributes":{"name":"Ops","parent_id":"not-a-uuid"}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid parent id, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/folders/create", []byte(`{"data":{"type":"file_manager_folder","attributes":{"name":"Ops","parent_id":"`+mustUUIDStringFileManager(t, parentID)+`"}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create folder action error, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/folders/get/not-a-uuid", nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 invalid get folder uuid path handling, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/folders/get/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 get folder action error, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/folders/get-all?parent_uuid=not-a-uuid", nil)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid list parent uuid, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/folders/get-all?parent_uuid="+mustUUIDStringFileManager(t, parentID), nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 list folders action error, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/get-all?parent_uuid=not-a-uuid", nil)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid list all parent uuid, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/get-all?parent_uuid="+mustUUIDStringFileManager(t, parentID), nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 list all action error, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/folders/trash/get-all?parent_uuid=not-a-uuid", nil)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid trash parent uuid, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/folders/trash/get-all?parent_uuid="+mustUUIDStringFileManager(t, parentID), nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 list trash folders action error, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/update/not-a-uuid", []byte(`{}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid update folder uuid, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/update/"+mustUUIDStringFileManager(t, folderID), []byte(`{`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 invalid update folder json, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/update/"+mustUUIDStringFileManager(t, folderID), []byte(`{"data":{"type":"file_manager_folder","id":"other","attributes":{"name":"Renamed"}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 update folder id mismatch, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/update/"+mustUUIDStringFileManager(t, folderID), []byte(`{"data":{"type":"file_manager_folder","id":"`+mustUUIDStringFileManager(t, folderID)+`","attributes":{}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 update folder no attrs, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/update/"+mustUUIDStringFileManager(t, folderID), []byte(`{"data":{"type":"file_manager_folder","id":"`+mustUUIDStringFileManager(t, folderID)+`","attributes":{"parent_id":"not-a-uuid"}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid update folder parent id, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/update/"+mustUUIDStringFileManager(t, folderID), []byte(`{"data":{"type":"file_manager_folder","id":"`+mustUUIDStringFileManager(t, folderID)+`","attributes":{"name":"Renamed"}}}`))
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404 update folder action error, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/folders/delete/not-a-uuid", nil)
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404 invalid delete folder uuid path handling, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/folders/delete/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404 delete folder action error, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/restore/not-a-uuid", nil)
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404 invalid restore folder uuid path handling, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/folders/restore/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404 restore folder action error, got %d", resp.StatusCode)
		}
	})

	t.Run("bulk handlers and helper error branches", func(t *testing.T) {
		folderID := uuidv7.MustBytes()
		fileID := uuidv7.MustBytes()
		parentID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		svc := &fileManagerFolderHandlerServiceMock{
			moveFolderFn: func(_ context.Context, id []byte, _ filemanager.MoveFolderParams) (*filemanager.Folder, error) {
				if bytes.Equal(id, folderID) {
					return nil, errors.New("move folder failure")
				}
				return nil, filemanager.ErrFolderNotFound
			},
			moveFileFn: func(_ context.Context, id []byte, _ filemanager.MoveFileParams) (*filemanager.File, error) {
				if bytes.Equal(id, fileID) {
					return nil, errors.New("move file failure")
				}
				return nil, filemanager.ErrFileNotFound
			},
			deleteFolderFn: func(_ context.Context, id []byte, _ []byte, _ *time.Time) error {
				if bytes.Equal(id, folderID) {
					return errors.New("delete folder failure")
				}
				return filemanager.ErrFolderNotFound
			},
			deleteFileFn: func(_ context.Context, id []byte, _ []byte, _ *time.Time) error {
				if bytes.Equal(id, fileID) {
					return errors.New("delete file failure")
				}
				return filemanager.ErrFileNotFound
			},
			restoreFolderFn: func(_ context.Context, id []byte, _ []byte, _ bool, _ []byte) error {
				if bytes.Equal(id, folderID) {
					return errors.New("restore folder failure")
				}
				return filemanager.ErrFolderNotFound
			},
			restoreFileFn: func(_ context.Context, id []byte, _ []byte, _ bool, _ []byte) error {
				if bytes.Equal(id, fileID) {
					return nil
				}
				return filemanager.ErrFileNotFound
			},
			getFileFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
				if bytes.Equal(id, fileID) {
					return nil, errors.New("get file failure")
				}
				return nil, nil
			},
			getFolderFn: func(_ context.Context, id []byte) (*filemanager.Folder, error) {
				if bytes.Equal(id, parentID) {
					return nil, errors.New("breadcrumb failure")
				}
				return nil, nil
			},
			getRootFn: func(context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID}, nil
			},
			purgeFolderFn: func(_ context.Context, id []byte) error {
				if bytes.Equal(id, folderID) {
					return errors.New("purge folder failure")
				}
				return filemanager.ErrFolderNotFound
			},
			purgeFileFn: func(_ context.Context, id []byte) error {
				if bytes.Equal(id, fileID) {
					return filemanager.ErrFileNotFound
				}
				return errors.New("purge file failure")
			},
		}
		h := NewFileManagerFolderHandler(svc, nil)
		app := newFileManagerFolderHandlerMoveTestApp(h, uuidv7.MustBytes())

		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/move", []byte(`{"data":[{"uuid":"`+mustUUIDStringFileManager(t, folderID)+`","destination_folder_uuid":"`+mustUUIDStringFileManager(t, parentID)+`"},{"uuid":"`+mustUUIDStringFileManager(t, fileID)+`","destination_folder_uuid":null},{"uuid":"not-a-uuid","destination_folder_uuid":null}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 move bulk mixed errors, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/delete", []byte(`{"data":[{"uuid":"`+mustUUIDStringFileManager(t, folderID)+`"},{"uuid":"`+mustUUIDStringFileManager(t, fileID)+`"}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 delete bulk mixed errors, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/restore", []byte(`{"data":[{"uuid":"`+mustUUIDStringFileManager(t, folderID)+`"},{"uuid":"`+mustUUIDStringFileManager(t, fileID)+`"}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 restore bulk mixed errors, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/purge-delete", []byte(`{"data":[{"uuid":"`+mustUUIDStringFileManager(t, folderID)+`"},{"uuid":"`+mustUUIDStringFileManager(t, fileID)+`"}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 purge bulk mixed errors, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/get-all?parent_uuid="+mustUUIDStringFileManager(t, parentID), nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 breadcrumb error, got %d", resp.StatusCode)
		}

		if fileManagerMoveBulkActionErrorItem(0, errFileManagerNodeNotFound).Error.Status != http.StatusNotFound {
			t.Fatalf("expected move action helper to map not found")
		}
		item := fileManagerPurgeBulkSystemErrorItem(0, http.StatusConflict, "Conflict", "duplicate")
		if item.Error == nil || item.Error.Status != http.StatusConflict {
			t.Fatalf("expected purge helper error item")
		}
	})

	t.Run("remaining folder branches", func(t *testing.T) {
		folderID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		parentID := uuidv7.MustBytes()
		now := time.Now().UTC()

		appNotFound := newFileManagerFolderHandlerMoveTestApp(NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			getFolderFn: func(context.Context, []byte) (*filemanager.Folder, error) { return nil, nil },
		}, nil), uuidv7.MustBytes())
		resp, _ := doJSONRequestFileManagerFile(t, appNotFound, http.MethodGet, "/api/v1/file-manager/folders/get/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404 get folder not found, got %d", resp.StatusCode)
		}

		appRestoreRootErr := newFileManagerFolderHandlerMoveTestApp(NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFolderFn: func(_ context.Context, id []byte) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: id, ParentID: parentID, Name: "Ops", CreatedAt: now, UpdatedAt: now}, nil
			},
			getRootFn: func(context.Context) (*filemanager.Folder, error) { return nil, errors.New("root fail") },
		}, nil), uuidv7.MustBytes())
		resp, _ = doJSONRequestFileManagerFile(t, appRestoreRootErr, http.MethodPatch, "/api/v1/file-manager/folders/restore/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 restore folder root error, got %d", resp.StatusCode)
		}

		appRestoreNonRoot := newFileManagerFolderHandlerMoveTestApp(NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFolderFn: func(_ context.Context, id []byte) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: id, ParentID: parentID, Name: "Ops", CreatedAt: now, UpdatedAt: now}, nil
			},
			getRootFn: func(context.Context) (*filemanager.Folder, error) { return &filemanager.Folder{ID: rootID}, nil },
		}, nil), uuidv7.MustBytes())
		resp, body := doJSONRequestFileManagerFile(t, appRestoreNonRoot, http.MethodPatch, "/api/v1/file-manager/folders/restore/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 restore folder non-root, got %d", resp.StatusCode)
		}
		out := decodeJSONMapFileManagerFile(t, body)
		data := mapField(t, out["data"], "data")
		attrs := mapField(t, data["attributes"], "attributes")
		location := mapField(t, attrs["location"], "location")
		if location["folder_id"] != mustUUIDStringFileManager(t, parentID) {
			t.Fatalf("expected restore folder location")
		}

		appRestoreMissing := newFileManagerFolderHandlerMoveTestApp(NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFolderFn:     func(context.Context, []byte) (*filemanager.Folder, error) { return nil, nil },
		}, nil), uuidv7.MustBytes())
		resp, _ = doJSONRequestFileManagerFile(t, appRestoreMissing, http.MethodPatch, "/api/v1/file-manager/folders/restore/"+mustUUIDStringFileManager(t, folderID), nil)
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404 restore folder missing row, got %d", resp.StatusCode)
		}

		appDeleteValidation := newFileManagerFolderHandlerMoveTestApp(NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{}, nil), uuidv7.MustBytes())
		resp, _ = doJSONRequestFileManagerFile(t, appDeleteValidation, http.MethodDelete, "/api/v1/file-manager/delete", []byte(`{`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 delete bulk invalid json, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, appDeleteValidation, http.MethodDelete, "/api/v1/file-manager/delete", []byte(`{"data":[]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 delete bulk empty data, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, appDeleteValidation, http.MethodDelete, "/api/v1/file-manager/delete", []byte(`{"data":[{"uuid":""}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 delete bulk item validation error, got %d", resp.StatusCode)
		}

		appRestoreValidation := newFileManagerFolderHandlerMoveTestApp(NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return filemanager.ErrFolderNotFound },
			restoreFileFn:   func(context.Context, []byte, []byte, bool, []byte) error { return filemanager.ErrFileNotFound },
		}, nil), uuidv7.MustBytes())
		resp, _ = doJSONRequestFileManagerFile(t, appRestoreValidation, http.MethodPatch, "/api/v1/file-manager/restore", []byte(`{`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 restore bulk invalid json, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, appRestoreValidation, http.MethodPatch, "/api/v1/file-manager/restore", []byte(`{"data":[]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 restore bulk empty data, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, appRestoreValidation, http.MethodPatch, "/api/v1/file-manager/restore", []byte(`{"data":[{"uuid":"`+mustUUIDStringFileManager(t, folderID)+`"}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 restore bulk not found item, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, appRestoreValidation, http.MethodPatch, "/api/v1/file-manager/restore", []byte(`{"data":[{"uuid":"not-a-uuid"}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 restore bulk invalid uuid item, got %d", resp.StatusCode)
		}

		appPurgeValidation := newFileManagerFolderHandlerMoveTestApp(NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{}, nil), uuidv7.MustBytes())
		resp, _ = doJSONRequestFileManagerFile(t, appPurgeValidation, http.MethodDelete, "/api/v1/file-manager/purge-delete", []byte(`{`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 purge bulk invalid json, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, appPurgeValidation, http.MethodDelete, "/api/v1/file-manager/purge-delete", []byte(`{"data":[]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 purge bulk empty data, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, appPurgeValidation, http.MethodDelete, "/api/v1/file-manager/purge-delete", []byte(`{"data":[{"uuid":""}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 purge bulk item validation error, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, appDeleteValidation, http.MethodDelete, "/api/v1/file-manager/delete", []byte(`{"data":[{"uuid":"not-a-uuid"}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 delete bulk invalid uuid item, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, appPurgeValidation, http.MethodDelete, "/api/v1/file-manager/purge-delete", []byte(`{"data":[{"uuid":"not-a-uuid"}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 purge bulk invalid uuid item, got %d", resp.StatusCode)
		}
	})

	t.Run("restore node helper edge cases", func(t *testing.T) {
		nodeID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		parentID := uuidv7.MustBytes()

		hFolderGetErr := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFolderFn:     func(context.Context, []byte) (*filemanager.Folder, error) { return nil, errors.New("get folder fail") },
		}, nil)
		if _, _, err := hFolderGetErr.restoreNodeAutoDetect(context.Background(), nodeID, nil); err == nil {
			t.Fatalf("expected restore folder get error")
		}

		hFolderNil := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFolderFn:     func(context.Context, []byte) (*filemanager.Folder, error) { return nil, nil },
		}, nil)
		if _, _, err := hFolderNil.restoreNodeAutoDetect(context.Background(), nodeID, nil); !errors.Is(err, errFileManagerNodeNotFound) {
			t.Fatalf("expected restore folder missing node, got %v", err)
		}

		hFolderRootErr := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFolderFn: func(_ context.Context, id []byte) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: id, ParentID: parentID}, nil
			},
			getRootFn: func(context.Context) (*filemanager.Folder, error) { return nil, errors.New("root fail") },
		}, nil)
		if _, _, err := hFolderRootErr.restoreNodeAutoDetect(context.Background(), nodeID, nil); err == nil {
			t.Fatalf("expected restore folder root error")
		}

		hFolderRoot := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFolderFn: func(_ context.Context, id []byte) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: id, ParentID: rootID}, nil
			},
			getRootFn: func(context.Context) (*filemanager.Folder, error) { return &filemanager.Folder{ID: rootID}, nil },
		}, nil)
		nodeType, location, err := hFolderRoot.restoreNodeAutoDetect(context.Background(), nodeID, nil)
		if err != nil || nodeType != "folder" || location != "" {
			t.Fatalf("expected restore folder root success, got type=%s location=%s err=%v", nodeType, location, err)
		}

		hFileNil := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return filemanager.ErrFolderNotFound },
			restoreFileFn:   func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFileFn:       func(context.Context, []byte) (*filemanager.File, error) { return nil, nil },
		}, nil)
		if _, _, err := hFileNil.restoreNodeAutoDetect(context.Background(), nodeID, nil); !errors.Is(err, errFileManagerNodeNotFound) {
			t.Fatalf("expected restore file missing node, got %v", err)
		}

		hFileRootErr := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return filemanager.ErrFolderNotFound },
			restoreFileFn:   func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFileFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
				return &filemanager.File{ID: id, FolderID: parentID}, nil
			},
			getRootFn: func(context.Context) (*filemanager.Folder, error) { return nil, errors.New("root fail") },
		}, nil)
		if _, _, err := hFileRootErr.restoreNodeAutoDetect(context.Background(), nodeID, nil); err == nil {
			t.Fatalf("expected restore file root error")
		}

		hFileGetErr := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return filemanager.ErrFolderNotFound },
			restoreFileFn:   func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFileFn:       func(context.Context, []byte) (*filemanager.File, error) { return nil, errors.New("get file fail") },
		}, nil)
		if _, _, err := hFileGetErr.restoreNodeAutoDetect(context.Background(), nodeID, nil); err == nil {
			t.Fatalf("expected restore file get error")
		}

		hRestoreFileErr := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return filemanager.ErrFolderNotFound },
			restoreFileFn:   func(context.Context, []byte, []byte, bool, []byte) error { return errors.New("restore file fail") },
		}, nil)
		if _, _, err := hRestoreFileErr.restoreNodeAutoDetect(context.Background(), nodeID, nil); err == nil {
			t.Fatalf("expected restore file action error")
		}

		hFileNonRoot := NewFileManagerFolderHandler(&fileManagerFolderHandlerServiceMock{
			restoreFolderFn: func(context.Context, []byte, []byte, bool, []byte) error { return filemanager.ErrFolderNotFound },
			restoreFileFn:   func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFileFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
				return &filemanager.File{ID: id, FolderID: parentID}, nil
			},
			getRootFn: func(context.Context) (*filemanager.Folder, error) { return &filemanager.Folder{ID: rootID}, nil },
		}, nil)
		nodeType2, location2, err := hFileNonRoot.restoreNodeAutoDetect(context.Background(), nodeID, nil)
		if err != nil || nodeType2 != "file" || location2 != mustUUIDStringFileManager(t, parentID) {
			t.Fatalf("expected restore file non-root location, got type=%s location=%s err=%v", nodeType2, location2, err)
		}
	})
}
