package handlers

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

	"github.com/gofiber/fiber/v2"

	"wucher/internal/domain/base"
	filemanager "wucher/internal/domain/file_manager"
	"wucher/internal/domain/helicopter"
	"wucher/internal/shared/pkg/uuidv7"
	responsedto "wucher/internal/transport/http/dto/response"
)

type fileManagerFileHandlerServiceMock struct {
	filemanager.Service

	requestUploadIntentFn func(context.Context, filemanager.RequestFileUploadIntentParams) (*filemanager.FileUploadIntentGrant, error)
	completeUploadFn      func(context.Context, filemanager.CompleteFileUploadIntentParams) (*filemanager.File, error)
	cancelUploadFn        func(context.Context, filemanager.CancelFileUploadIntentParams) error
	createFromIntentFn    func(context.Context, filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error)
	getOrCreateRootFn     func(context.Context) (*filemanager.Folder, error)
	getFolderByParentFn   func(context.Context, []byte, string) (*filemanager.Folder, error)
	createFolderNodeFn    func(context.Context, filemanager.CreateFolderParams) (*filemanager.Folder, error)
	getFileByIDFn         func(context.Context, []byte) (*filemanager.File, error)
	getFileByFolderNameFn func(context.Context, []byte, string) (*filemanager.File, error)
	listFilesByFolderFn   func(context.Context, []byte, string, int, int) ([]filemanager.File, int64, error)
	listTrashFilesFn      func(context.Context, []byte, string, int, int) ([]filemanager.File, int64, error)
	moveFileFn            func(context.Context, []byte, filemanager.MoveFileParams) (*filemanager.File, error)
	deleteFileFn          func(context.Context, []byte, []byte, *time.Time) error
	purgeFileNodeFn       func(context.Context, []byte) error
	restoreFileFn         func(context.Context, []byte, []byte, bool, []byte) error

	requestUploadIntentCalls int
	completeUploadCalls      int
	cancelUploadCalls        int
	createFromIntentCalls    int

	lastRequestUploadIntent *filemanager.RequestFileUploadIntentParams
	lastCompleteUpload      *filemanager.CompleteFileUploadIntentParams
	lastCancelUpload        *filemanager.CancelFileUploadIntentParams
	lastCreateFromIntent    *filemanager.CreateFileFromUploadIntentParams
	lastCreateFolderNode    *filemanager.CreateFolderParams
}

func (m *fileManagerFileHandlerServiceMock) RequestFileUploadIntent(ctx context.Context, params filemanager.RequestFileUploadIntentParams) (*filemanager.FileUploadIntentGrant, error) {
	m.requestUploadIntentCalls++
	copied := params
	copied.ActorID = append([]byte(nil), params.ActorID...)
	m.lastRequestUploadIntent = &copied

	if m.requestUploadIntentFn != nil {
		return m.requestUploadIntentFn(ctx, params)
	}
	return nil, nil
}

func (m *fileManagerFileHandlerServiceMock) CompleteFileUploadIntent(ctx context.Context, params filemanager.CompleteFileUploadIntentParams) (*filemanager.File, error) {
	m.completeUploadCalls++
	copied := params
	copied.UploadIntentID = append([]byte(nil), params.UploadIntentID...)
	copied.ActorID = append([]byte(nil), params.ActorID...)
	m.lastCompleteUpload = &copied

	if m.completeUploadFn != nil {
		return m.completeUploadFn(ctx, params)
	}
	return nil, nil
}

func (m *fileManagerFileHandlerServiceMock) CancelFileUploadIntent(ctx context.Context, params filemanager.CancelFileUploadIntentParams) error {
	m.cancelUploadCalls++
	copied := params
	copied.UploadIntentID = append([]byte(nil), params.UploadIntentID...)
	copied.ActorID = append([]byte(nil), params.ActorID...)
	m.lastCancelUpload = &copied

	if m.cancelUploadFn != nil {
		return m.cancelUploadFn(ctx, params)
	}
	return nil
}

func (m *fileManagerFileHandlerServiceMock) CreateFileNodeFromUploadIntent(ctx context.Context, params filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
	m.createFromIntentCalls++
	copied := params
	copied.UploadIntentID = append([]byte(nil), params.UploadIntentID...)
	copied.FolderID = append([]byte(nil), params.FolderID...)
	copied.ActorID = append([]byte(nil), params.ActorID...)
	m.lastCreateFromIntent = &copied

	if m.createFromIntentFn != nil {
		return m.createFromIntentFn(ctx, params)
	}
	return nil, nil
}

func (m *fileManagerFileHandlerServiceMock) GetOrCreateSystemRootFolder(ctx context.Context) (*filemanager.Folder, error) {
	if m.getOrCreateRootFn != nil {
		return m.getOrCreateRootFn(ctx)
	}
	return nil, nil
}

func (m *fileManagerFileHandlerServiceMock) GetFolderByParentAndName(ctx context.Context, parentID []byte, nameNormalized string) (*filemanager.Folder, error) {
	if m.getFolderByParentFn != nil {
		return m.getFolderByParentFn(ctx, parentID, nameNormalized)
	}
	return nil, nil
}

func (m *fileManagerFileHandlerServiceMock) CreateFolderNode(ctx context.Context, params filemanager.CreateFolderParams) (*filemanager.Folder, error) {
	copied := params
	copied.ParentID = append([]byte(nil), params.ParentID...)
	copied.ActorID = append([]byte(nil), params.ActorID...)
	m.lastCreateFolderNode = &copied

	if m.createFolderNodeFn != nil {
		return m.createFolderNodeFn(ctx, params)
	}
	return nil, nil
}

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

func (m *fileManagerFileHandlerServiceMock) GetFileByFolderAndName(ctx context.Context, folderID []byte, nameNormalized string) (*filemanager.File, error) {
	if m.getFileByFolderNameFn != nil {
		return m.getFileByFolderNameFn(ctx, folderID, nameNormalized)
	}
	return nil, nil
}

func (m *fileManagerFileHandlerServiceMock) ListFilesByFolder(ctx context.Context, folderID []byte, sort string, limit, offset int) ([]filemanager.File, int64, error) {
	if m.listFilesByFolderFn != nil {
		return m.listFilesByFolderFn(ctx, folderID, sort, limit, offset)
	}
	return nil, 0, nil
}

func (m *fileManagerFileHandlerServiceMock) ListTrashFilesByFolder(ctx context.Context, folderID []byte, sort string, limit, offset int) ([]filemanager.File, int64, error) {
	if m.listTrashFilesFn != nil {
		return m.listTrashFilesFn(ctx, folderID, sort, limit, offset)
	}
	return nil, 0, nil
}

func (m *fileManagerFileHandlerServiceMock) 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 *fileManagerFileHandlerServiceMock) 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 *fileManagerFileHandlerServiceMock) PurgeFileNode(ctx context.Context, id []byte) error {
	if m.purgeFileNodeFn != nil {
		return m.purgeFileNodeFn(ctx, id)
	}
	return nil
}

func (m *fileManagerFileHandlerServiceMock) 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
}

type fileManagerFileHandlerAircraftServiceMock struct {
	helicopter.Service
	getByIDFn func(context.Context, []byte) (*helicopter.Helicopter, error)
}

func (m *fileManagerFileHandlerAircraftServiceMock) GetByID(ctx context.Context, id []byte) (*helicopter.Helicopter, error) {
	if m.getByIDFn != nil {
		return m.getByIDFn(ctx, id)
	}
	return nil, nil
}

type fileManagerFileHandlerBaseServiceMock struct {
	base.Service
	getByIDFn func(context.Context, []byte, string) (*base.Base, error)
}

func (m *fileManagerFileHandlerBaseServiceMock) GetBaseByID(ctx context.Context, id []byte, categoryType string) (*base.Base, error) {
	if m.getByIDFn != nil {
		return m.getByIDFn(ctx, id, categoryType)
	}
	return nil, nil
}

func newFileManagerFileHandlerTestApp(h *FileManagerFileHandler, 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.Post("/api/v1/file-manager/files/create", h.CreateFile)
	app.Post("/api/v1/file-manager/files/create-aircraft-image", h.CreateAircraftImage)
	app.Post("/api/v1/file-manager/files/create-base-image", h.CreateBaseImage)
	app.Post("/api/v1/file-manager/files/create-dul-image", h.CreateDULImage)
	app.Post("/api/v1/file-manager/files/upload", h.UploadFile)
	app.Post("/api/v1/file-manager/files/upload-aircraft-image", h.UploadAircraftImage)
	app.Post("/api/v1/file-manager/files/upload-base-image", h.UploadBaseImage)
	app.Delete("/api/v1/file-manager/files/cancel-upload", h.CancelUpload)
	app.Get("/api/v1/file-manager/files/get/:uuid", h.GetFile)
	app.Get("/api/v1/file-manager/files/get-all", h.ListFiles)
	app.Get("/api/v1/file-manager/files/trash/get-all", h.ListTrashFiles)
	app.Patch("/api/v1/file-manager/files/update/:uuid", h.UpdateFile)
	app.Delete("/api/v1/file-manager/files/delete/:uuid", h.DeleteFile)
	app.Patch("/api/v1/file-manager/files/restore/:uuid", h.RestoreFile)
	return app
}

func newFileManagerFileHandlerInternalCompatTestApp(h *FileManagerFileHandler, actor []byte) *fiber.App {
	app := newFileManagerFileHandlerTestApp(h, actor)
	app.Post("/api/v1/file-manager/files/upload/complete", h.CompleteUploadFile)
	return app
}

func doJSONRequestFileManagerFile(t *testing.T, app *fiber.App, method, path string, body []byte) (*http.Response, []byte) {
	t.Helper()

	req, err := http.NewRequest(method, path, bytes.NewReader(body))
	if err != nil {
		t.Fatalf("new request: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("read response body: %v", err)
	}

	return resp, respBody
}

func decodeJSONMapFileManagerFile(t *testing.T, body []byte) map[string]any {
	t.Helper()

	var out map[string]any
	if err := json.Unmarshal(body, &out); err != nil {
		t.Fatalf("decode json: %v\nbody=%s", err, string(body))
	}
	return out
}

func mapField(t *testing.T, raw any, name string) map[string]any {
	t.Helper()

	m, ok := raw.(map[string]any)
	if !ok {
		t.Fatalf("%s is not object: %#v", name, raw)
	}
	return m
}

func arrayField(t *testing.T, raw any, name string) []any {
	t.Helper()

	arr, ok := raw.([]any)
	if !ok {
		t.Fatalf("%s is not array: %#v", name, raw)
	}
	return arr
}

func stringField(t *testing.T, m map[string]any, key string) string {
	t.Helper()

	v, ok := m[key]
	if !ok {
		t.Fatalf("missing %s", key)
	}
	s, ok := v.(string)
	if !ok {
		t.Fatalf("%s is not string: %#v", key, v)
	}
	return s
}

func numberField(t *testing.T, m map[string]any, key string) float64 {
	t.Helper()

	v, ok := m[key]
	if !ok {
		t.Fatalf("missing %s", key)
	}
	n, ok := v.(float64)
	if !ok {
		t.Fatalf("%s is not number: %#v", key, v)
	}
	return n
}

func mustUUIDStringFileManager(t *testing.T, id []byte) string {
	t.Helper()

	s, err := uuidv7.BytesToString(id)
	if err != nil {
		t.Fatalf("uuid to string: %v", err)
	}
	return s
}

func TestFileManagerFileHandlerUploadFile(t *testing.T) {
	t.Run("success returns presigned upload intent", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		uploadIntentID := uuidv7.MustBytes()
		expiresAt := time.Now().UTC().Add(15 * time.Minute)

		svc := &fileManagerFileHandlerServiceMock{
			requestUploadIntentFn: func(_ context.Context, _ filemanager.RequestFileUploadIntentParams) (*filemanager.FileUploadIntentGrant, error) {
				return &filemanager.FileUploadIntentGrant{
					UploadIntentID: uploadIntentID,
					Bucket:         "wucher-file-dev",
					ObjectKey:      "files/2026/04/05/test-policy.pdf",
					Name:           "policy.pdf",
					MimeType:       "application/pdf",
					SizeBytes:      1024,
					Method:         "put",
					UploadURL:      "https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/test-policy.pdf",
					Headers: map[string]string{
						"Content-Type": "application/pdf",
					},
					ExpiresAt: expiresAt,
				}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, actorID)

		body := []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"policy.pdf","content_type":"application/pdf","size_bytes":1024}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", body)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d body=%s", resp.StatusCode, string(respBody))
		}

		if svc.lastRequestUploadIntent == nil {
			t.Fatalf("expected request upload intent to be called")
		}
		if svc.lastRequestUploadIntent.Name != "policy.pdf" {
			t.Fatalf("unexpected name: %q", svc.lastRequestUploadIntent.Name)
		}
		if svc.lastRequestUploadIntent.ContentType != "application/pdf" {
			t.Fatalf("unexpected content_type: %q", svc.lastRequestUploadIntent.ContentType)
		}
		if svc.lastRequestUploadIntent.SizeBytes != 1024 {
			t.Fatalf("unexpected size_bytes: %d", svc.lastRequestUploadIntent.SizeBytes)
		}
		if !bytes.Equal(svc.lastRequestUploadIntent.ActorID, actorID) {
			t.Fatalf("unexpected actor_id")
		}

		payload := decodeJSONMapFileManagerFile(t, respBody)
		dataItems := arrayField(t, payload["data"], "data")
		if len(dataItems) != 1 {
			t.Fatalf("expected one data item, got %d", len(dataItems))
		}
		item := mapField(t, dataItems[0], "data[0]")
		if v, ok := item["success"].(bool); !ok || !v {
			t.Fatalf("expected success=true, got %#v", item["success"])
		}
		resource := mapField(t, item["resource"], "data[0].resource")
		attrs := mapField(t, resource["attributes"], "data[0].resource.attributes")

		if got := stringField(t, resource, "type"); got != "file_manager_file_upload_intent" {
			t.Fatalf("unexpected data[0].resource.type: %q", got)
		}

		if got := stringField(t, attrs, "upload_intent_uuid"); got == "" {
			t.Fatalf("upload_intent_uuid should not be empty")
		}
		if got := stringField(t, attrs, "upload_url"); got == "" {
			t.Fatalf("upload_url should not be empty")
		}
		if got := stringField(t, attrs, "method"); got != "PUT" {
			t.Fatalf("unexpected method: %q", got)
		}
		object := mapField(t, attrs["object"], "data.attributes.object")
		if got := stringField(t, object, "bucket"); got != "wucher-file-dev" {
			t.Fatalf("unexpected bucket: %q", got)
		}
		if got := stringField(t, object, "key"); got == "" {
			t.Fatalf("object.key should not be empty")
		}
		if got := stringField(t, attrs, "expires_at"); got == "" {
			t.Fatalf("expires_at should not be empty")
		}
		fileInfo := mapField(t, attrs["file"], "data.attributes.file")
		if got := stringField(t, fileInfo, "name"); got != "policy.pdf" {
			t.Fatalf("unexpected file.name: %q", got)
		}
		if got := stringField(t, fileInfo, "mime_type"); got != "application/pdf" {
			t.Fatalf("unexpected file.mime_type: %q", got)
		}
		if got := numberField(t, fileInfo, "size_bytes"); got != 1024 {
			t.Fatalf("unexpected file.size_bytes in response: %.0f", got)
		}
		expiresIn := numberField(t, attrs, "expires_in_seconds")
		if expiresIn <= 0 || expiresIn > 900 {
			t.Fatalf("unexpected expires_in_seconds: %v", expiresIn)
		}

		headers := mapField(t, attrs["required_headers"], "data.attributes.required_headers")
		if got := stringField(t, headers, "content-type"); got != "application/pdf" {
			t.Fatalf("unexpected required_headers.content-type: %q", got)
		}

		meta := mapField(t, payload["meta"], "meta")
		if got := numberField(t, meta, "total"); got != 1 {
			t.Fatalf("unexpected meta.total: %.0f", got)
		}
		if got := numberField(t, meta, "success"); got != 1 {
			t.Fatalf("unexpected meta.success: %.0f", got)
		}
		if got := numberField(t, meta, "failed"); got != 0 {
			t.Fatalf("unexpected meta.failed: %.0f", got)
		}
	})

	t.Run("invalid body returns 422", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, nil)

		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", []byte(`{}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
		if svc.requestUploadIntentCalls != 0 {
			t.Fatalf("request upload intent should not be called")
		}
	})

	t.Run("storage unavailable maps to 503", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{
			requestUploadIntentFn: func(context.Context, filemanager.RequestFileUploadIntentParams) (*filemanager.FileUploadIntentGrant, error) {
				return nil, filemanager.ErrStorageUnavailable
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, nil)

		body := []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"policy.pdf","content_type":"application/pdf","size_bytes":1024}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", body)
		if resp.StatusCode != http.StatusServiceUnavailable {
			t.Fatalf("expected 503, got %d", resp.StatusCode)
		}

		payload := decodeJSONMapFileManagerFile(t, respBody)
		dataItems := arrayField(t, payload["data"], "data")
		if len(dataItems) != 1 {
			t.Fatalf("expected one data item, got %d", len(dataItems))
		}
		item := mapField(t, dataItems[0], "data[0]")
		errObj := mapField(t, item["error"], "data[0].error")
		if got := numberField(t, errObj, "status"); got != 503 {
			t.Fatalf("expected item error status 503, got %.0f", got)
		}
	})

	t.Run("partial success returns 207", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{
			requestUploadIntentFn: func(_ context.Context, _ filemanager.RequestFileUploadIntentParams) (*filemanager.FileUploadIntentGrant, error) {
				return &filemanager.FileUploadIntentGrant{
					UploadIntentID: uuidv7.MustBytes(),
					Bucket:         "wucher-file-dev",
					ObjectKey:      "files/2026/04/05/test-policy.pdf",
					Name:           "policy.pdf",
					MimeType:       "application/pdf",
					SizeBytes:      1024,
					Method:         "PUT",
					UploadURL:      "https://example.local/upload",
					ExpiresAt:      time.Now().UTC().Add(15 * time.Minute),
				}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, nil)

		body := []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"policy.pdf","content_type":"application/pdf","size_bytes":1024}},{"type":"invalid_type","attributes":{"name":"bad.pdf","content_type":"application/pdf","size_bytes":1}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", body)
		if resp.StatusCode != http.StatusMultiStatus {
			t.Fatalf("expected 207, got %d body=%s", resp.StatusCode, string(respBody))
		}
		payload := decodeJSONMapFileManagerFile(t, respBody)
		meta := mapField(t, payload["meta"], "meta")
		if partial, ok := meta["partial_success"].(bool); !ok || !partial {
			t.Fatalf("expected meta.partial_success=true, got %#v", meta["partial_success"])
		}
	})
}

func TestFileManagerFileHandlerCompleteUploadFile(t *testing.T) {
	t.Run("success completes upload intent", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		uploadIntentID := uuidv7.MustBytes()
		fileID := uuidv7.MustBytes()
		folderID := uuidv7.MustBytes()
		now := time.Now().UTC()

		svc := &fileManagerFileHandlerServiceMock{
			completeUploadFn: func(_ context.Context, _ filemanager.CompleteFileUploadIntentParams) (*filemanager.File, error) {
				return &filemanager.File{
					ID:             fileID,
					FolderID:       folderID,
					Name:           "policy.pdf",
					NameNormalized: "policy.pdf",
					Extension:      "pdf",
					SizeBytes:      1024,
					MimeType:       "application/pdf",
					Status:         filemanager.FileStatusUploaded,
					CreatedAt:      now,
					CreatedBy:      actorID,
					UpdatedAt:      now,
					UpdatedBy:      actorID,
				}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerInternalCompatTestApp(h, actorID)

		uploadIntentUUID, err := uuidv7.BytesToString(uploadIntentID)
		if err != nil {
			t.Fatalf("upload intent uuid string: %v", err)
		}
		body := []byte(`{"data":{"type":"file_manager_file_upload_complete","attributes":{"upload_intent_uuid":"` + uploadIntentUUID + `"}}}`)

		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload/complete", body)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d body=%s", resp.StatusCode, string(respBody))
		}

		if svc.lastCompleteUpload == nil {
			t.Fatalf("expected complete upload to be called")
		}
		if !bytes.Equal(svc.lastCompleteUpload.UploadIntentID, uploadIntentID) {
			t.Fatalf("unexpected upload_intent_id")
		}
		if !bytes.Equal(svc.lastCompleteUpload.ActorID, actorID) {
			t.Fatalf("unexpected actor_id")
		}

		payload := decodeJSONMapFileManagerFile(t, respBody)
		data := mapField(t, payload["data"], "data")
		attrs := mapField(t, data["attributes"], "data.attributes")
		if got := stringField(t, data, "type"); got != "file_manager_file" {
			t.Fatalf("unexpected data.type: %q", got)
		}
		if got := stringField(t, attrs, "status"); got != filemanager.FileStatusUploaded {
			t.Fatalf("unexpected file status: %q", got)
		}
		if got := numberField(t, attrs, "size_bytes"); got != 1024 {
			t.Fatalf("unexpected size_bytes: %.0f", got)
		}
	})

	t.Run("invalid upload_intent_uuid returns 422", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerInternalCompatTestApp(h, nil)

		body := []byte(`{"data":{"type":"file_manager_file_upload_complete","attributes":{"upload_intent_uuid":"not-uuid"}}}`)
		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload/complete", body)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
		if svc.completeUploadCalls != 0 {
			t.Fatalf("complete upload should not be called")
		}
	})

	t.Run("upload intent not found maps to 404", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{
			completeUploadFn: func(context.Context, filemanager.CompleteFileUploadIntentParams) (*filemanager.File, error) {
				return nil, filemanager.ErrUploadIntentNotFound
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerInternalCompatTestApp(h, nil)

		uploadIntentUUID := mustUUIDStringFileManager(t, uuidv7.MustBytes())
		body := []byte(`{"data":{"type":"file_manager_file_upload_complete","attributes":{"upload_intent_uuid":"` + uploadIntentUUID + `"}}}`)
		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload/complete", body)
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404, got %d", resp.StatusCode)
		}
	})

	t.Run("object mismatch maps to 422", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{
			completeUploadFn: func(context.Context, filemanager.CompleteFileUploadIntentParams) (*filemanager.File, error) {
				return nil, filemanager.ErrUploadObjectMismatch
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerInternalCompatTestApp(h, nil)

		uploadIntentUUID := mustUUIDStringFileManager(t, uuidv7.MustBytes())
		body := []byte(`{"data":{"type":"file_manager_file_upload_complete","attributes":{"upload_intent_uuid":"` + uploadIntentUUID + `"}}}`)
		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload/complete", body)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("expired upload intent maps to 409", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{
			completeUploadFn: func(context.Context, filemanager.CompleteFileUploadIntentParams) (*filemanager.File, error) {
				return nil, filemanager.ErrUploadIntentExpired
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerInternalCompatTestApp(h, nil)

		uploadIntentUUID := mustUUIDStringFileManager(t, uuidv7.MustBytes())
		body := []byte(`{"data":{"type":"file_manager_file_upload_complete","attributes":{"upload_intent_uuid":"` + uploadIntentUUID + `"}}}`)
		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload/complete", body)
		if resp.StatusCode != http.StatusConflict {
			t.Fatalf("expected 409, got %d", resp.StatusCode)
		}
	})
}

func TestFileManagerFileHandlerCreateFile_FromUploadIntent(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		uploadIntentID := uuidv7.MustBytes()
		folderID := uuidv7.MustBytes()
		fileID := uuidv7.MustBytes()
		now := time.Now().UTC()

		svc := &fileManagerFileHandlerServiceMock{
			createFromIntentFn: func(_ context.Context, _ filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
				return &filemanager.File{
					ID:             fileID,
					FolderID:       folderID,
					Name:           "policy-final.pdf",
					NameNormalized: "policy-final.pdf",
					Extension:      "pdf",
					SizeBytes:      1024,
					MimeType:       "application/pdf",
					Status:         filemanager.FileStatusUploaded,
					CreatedAt:      now,
					CreatedBy:      actorID,
					UpdatedAt:      now,
					UpdatedBy:      actorID,
				}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, actorID)

		body := []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"` + mustUUIDStringFileManager(t, uploadIntentID) + `","folder_id":"` + mustUUIDStringFileManager(t, folderID) + `","name":"policy-final.pdf"}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", body)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d body=%s", resp.StatusCode, string(respBody))
		}
		if svc.createFromIntentCalls != 1 {
			t.Fatalf("expected create from intent called once, got %d", svc.createFromIntentCalls)
		}
		if svc.lastCreateFromIntent == nil {
			t.Fatalf("expected create params")
		}
		if !bytes.Equal(svc.lastCreateFromIntent.UploadIntentID, uploadIntentID) {
			t.Fatalf("unexpected upload intent id")
		}
		if !bytes.Equal(svc.lastCreateFromIntent.FolderID, folderID) {
			t.Fatalf("unexpected folder id")
		}
		if svc.lastCreateFromIntent.Name != "policy-final.pdf" {
			t.Fatalf("unexpected name: %q", svc.lastCreateFromIntent.Name)
		}
		if svc.lastCreateFromIntent.IsTemplate {
			t.Fatalf("expected create-from-template flow to keep is_template=false")
		}
	})

	t.Run("folder omitted defaults to root semantics", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{
			createFromIntentFn: func(_ context.Context, _ filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
				return &filemanager.File{ID: uuidv7.MustBytes(), FolderID: uuidv7.MustBytes(), Name: "policy.pdf", NameNormalized: "policy.pdf", Status: filemanager.FileStatusUploaded, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, uuidv7.MustBytes())

		body := []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"` + mustUUIDStringFileManager(t, uuidv7.MustBytes()) + `","name":"policy.pdf"}}]}`)
		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", body)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d", resp.StatusCode)
		}
		if svc.lastCreateFromIntent == nil {
			t.Fatalf("expected create params")
		}
		if svc.lastCreateFromIntent.FolderProvided {
			t.Fatalf("expected folder_provided=false when folder_id omitted")
		}
	})

	t.Run("invalid upload_intent_uuid returns 422", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, nil)

		body := []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"bad-uuid","name":"policy.pdf"}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", body)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}

		payload := decodeJSONMapFileManagerFile(t, respBody)
		dataItems := arrayField(t, payload["data"], "data")
		if len(dataItems) != 1 {
			t.Fatalf("expected one data item, got %d", len(dataItems))
		}
		item := mapField(t, dataItems[0], "data[0]")
		errObj := mapField(t, item["error"], "data[0].error")
		if got := numberField(t, errObj, "status"); got != 422 {
			t.Fatalf("expected item error status 422, got %.0f", got)
		}
		if svc.createFromIntentCalls != 0 {
			t.Fatalf("create should not be called on invalid uuid")
		}
	})

	t.Run("partial success returns 207", func(t *testing.T) {
		uploadIntentID := uuidv7.MustBytes()
		folderID := uuidv7.MustBytes()
		now := time.Now().UTC()

		svc := &fileManagerFileHandlerServiceMock{
			createFromIntentFn: func(_ context.Context, _ filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
				return &filemanager.File{
					ID:             uuidv7.MustBytes(),
					FolderID:       folderID,
					Name:           "ok.pdf",
					NameNormalized: "ok.pdf",
					Extension:      "pdf",
					SizeBytes:      100,
					MimeType:       "application/pdf",
					Status:         filemanager.FileStatusUploaded,
					CreatedAt:      now,
					UpdatedAt:      now,
				}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, nil)

		body := []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"` + mustUUIDStringFileManager(t, uploadIntentID) + `","name":"ok.pdf"}},{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"bad-uuid","name":"bad.pdf"}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", body)
		if resp.StatusCode != http.StatusMultiStatus {
			t.Fatalf("expected 207, got %d body=%s", resp.StatusCode, string(respBody))
		}
		payload := decodeJSONMapFileManagerFile(t, respBody)
		meta := mapField(t, payload["meta"], "meta")
		if partial, ok := meta["partial_success"].(bool); !ok || !partial {
			t.Fatalf("expected meta.partial_success=true, got %#v", meta["partial_success"])
		}
	})
}

func TestFileManagerFileHandlerCreateAircraftImage(t *testing.T) {
	t.Run("success stores file under root-aircraft-designation hierarchy", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		aircraftID := uuidv7.MustBytes()
		uploadIntentID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		aircraftFolderID := uuidv7.MustBytes()
		designationFolderID := uuidv7.MustBytes()
		fileID := uuidv7.MustBytes()
		now := time.Now().UTC()

		svc := &fileManagerFileHandlerServiceMock{
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID, Name: filemanager.SystemRootFolderName, NameNormalized: filemanager.SystemRootFolderName}, nil
			},
			getFolderByParentFn: func(_ context.Context, parentID []byte, nameNormalized string) (*filemanager.Folder, error) {
				if bytes.Equal(parentID, rootID) && nameNormalized == "aircraft" {
					return &filemanager.Folder{ID: aircraftFolderID, ParentID: rootID, Name: "aircraft", NameNormalized: "aircraft"}, nil
				}
				if bytes.Equal(parentID, aircraftFolderID) && nameNormalized == "oe-xhz" {
					return &filemanager.Folder{ID: designationFolderID, ParentID: aircraftFolderID, Name: "OE-XHZ", NameNormalized: "oe-xhz"}, nil
				}
				return nil, nil
			},
			createFromIntentFn: func(_ context.Context, params filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
				if !bytes.Equal(params.FolderID, designationFolderID) {
					t.Fatalf("expected designation folder id used, got %x", params.FolderID)
				}
				return &filemanager.File{
					ID:             fileID,
					FolderID:       designationFolderID,
					Name:           "xhz.png",
					NameNormalized: "xhz.png",
					Extension:      "png",
					SizeBytes:      1024,
					MimeType:       "image/png",
					Status:         filemanager.FileStatusUploaded,
					CreatedAt:      now,
					UpdatedAt:      now,
					CreatedBy:      actorID,
					UpdatedBy:      actorID,
				}, nil
			},
		}

		aircraftSvc := &fileManagerFileHandlerAircraftServiceMock{
			getByIDFn: func(_ context.Context, id []byte) (*helicopter.Helicopter, error) {
				if !bytes.Equal(id, aircraftID) {
					t.Fatalf("unexpected aircraft id: %x", id)
				}
				return &helicopter.Helicopter{ID: aircraftID, Designation: "OE-XHZ"}, nil
			},
		}

		h := NewFileManagerFileHandler(svc, nil).WithAircraftService(aircraftSvc)
		app := newFileManagerFileHandlerTestApp(h, actorID)

		body := []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"` + mustUUIDStringFileManager(t, uploadIntentID) + `","aircraft_uuid":"` + mustUUIDStringFileManager(t, aircraftID) + `","name":"xhz.png"}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", body)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d body=%s", resp.StatusCode, string(respBody))
		}
		if svc.lastCreateFromIntent == nil {
			t.Fatalf("expected create from upload intent called")
		}
		if !bytes.Equal(svc.lastCreateFromIntent.FolderID, designationFolderID) {
			t.Fatalf("unexpected folder id used: %x", svc.lastCreateFromIntent.FolderID)
		}
	})

	t.Run("invalid aircraft_uuid returns per-item validation error", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{}
		aircraftSvc := &fileManagerFileHandlerAircraftServiceMock{}
		h := NewFileManagerFileHandler(svc, nil).WithAircraftService(aircraftSvc)
		app := newFileManagerFileHandlerTestApp(h, uuidv7.MustBytes())

		body := []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"` + mustUUIDStringFileManager(t, uuidv7.MustBytes()) + `","aircraft_uuid":"bad-uuid","name":"xhz.png"}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", body)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d body=%s", resp.StatusCode, string(respBody))
		}
		payload := decodeJSONMapFileManagerFile(t, respBody)
		dataItems := arrayField(t, payload["data"], "data")
		item := mapField(t, dataItems[0], "data[0]")
		errObj := mapField(t, item["error"], "data[0].error")
		if got := numberField(t, errObj, "status"); got != 422 {
			t.Fatalf("expected item error status 422, got %.0f", got)
		}
		if svc.createFromIntentCalls != 0 {
			t.Fatalf("create should not be called")
		}
	})

	t.Run("replaces existing file with same name in designation folder", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		aircraftID := uuidv7.MustBytes()
		uploadIntentID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		aircraftFolderID := uuidv7.MustBytes()
		designationFolderID := uuidv7.MustBytes()
		oldFileID := uuidv7.MustBytes()
		newFileID := uuidv7.MustBytes()

		deleteCalls := 0
		purgeCalls := 0
		svc := &fileManagerFileHandlerServiceMock{
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID, Name: filemanager.SystemRootFolderName, NameNormalized: filemanager.SystemRootFolderName}, nil
			},
			getFolderByParentFn: func(_ context.Context, parentID []byte, nameNormalized string) (*filemanager.Folder, error) {
				if bytes.Equal(parentID, rootID) && nameNormalized == "aircraft" {
					return &filemanager.Folder{ID: aircraftFolderID, ParentID: rootID, Name: "aircraft", NameNormalized: "aircraft"}, nil
				}
				if bytes.Equal(parentID, aircraftFolderID) && nameNormalized == "oe-abc" {
					return &filemanager.Folder{ID: designationFolderID, ParentID: aircraftFolderID, Name: "OE-ABC", NameNormalized: "oe-abc"}, nil
				}
				return nil, nil
			},
			listFilesByFolderFn: func(_ context.Context, folderID []byte, sort string, limit, offset int) ([]filemanager.File, int64, error) {
				if !bytes.Equal(folderID, designationFolderID) {
					t.Fatalf("unexpected folder id lookup: %x", folderID)
				}
				if sort != "name ASC" {
					t.Fatalf("unexpected sort: %s", sort)
				}
				return []filemanager.File{{
					ID:             oldFileID,
					FolderID:       designationFolderID,
					Name:           "photo.png",
					NameNormalized: "photo.png",
				}}, 1, nil
			},
			deleteFileFn: func(_ context.Context, id []byte, deletedBy []byte, purgeAt *time.Time) error {
				deleteCalls++
				if !bytes.Equal(id, oldFileID) {
					t.Fatalf("unexpected deleted file id: %x", id)
				}
				if !bytes.Equal(deletedBy, actorID) {
					t.Fatalf("unexpected actor on delete: %x", deletedBy)
				}
				if purgeAt == nil {
					t.Fatalf("expected purgeAt to force immediate purge")
				}
				return nil
			},
			purgeFileNodeFn: func(_ context.Context, id []byte) error {
				purgeCalls++
				if !bytes.Equal(id, oldFileID) {
					t.Fatalf("unexpected purged file id: %x", id)
				}
				return nil
			},
			createFromIntentFn: func(_ context.Context, params filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
				if !bytes.Equal(params.FolderID, designationFolderID) {
					t.Fatalf("expected designation folder id used, got %x", params.FolderID)
				}
				return &filemanager.File{
					ID:             newFileID,
					FolderID:       designationFolderID,
					Name:           "photo.png",
					NameNormalized: "photo.png",
					Extension:      "png",
					MimeType:       "image/png",
					Status:         filemanager.FileStatusUploaded,
					CreatedAt:      time.Now().UTC(),
					UpdatedAt:      time.Now().UTC(),
					CreatedBy:      actorID,
					UpdatedBy:      actorID,
				}, nil
			},
		}
		aircraftSvc := &fileManagerFileHandlerAircraftServiceMock{
			getByIDFn: func(_ context.Context, id []byte) (*helicopter.Helicopter, error) {
				if !bytes.Equal(id, aircraftID) {
					t.Fatalf("unexpected aircraft id: %x", id)
				}
				return &helicopter.Helicopter{ID: aircraftID, Designation: "OE-ABC"}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil).WithAircraftService(aircraftSvc)
		app := newFileManagerFileHandlerTestApp(h, actorID)

		body := []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"` + mustUUIDStringFileManager(t, uploadIntentID) + `","aircraft_uuid":"` + mustUUIDStringFileManager(t, aircraftID) + `","name":"photo.png"}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", body)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d body=%s", resp.StatusCode, string(respBody))
		}
		if deleteCalls != 1 {
			t.Fatalf("expected delete old file called once, got %d", deleteCalls)
		}
		if purgeCalls != 1 {
			t.Fatalf("expected purge old file called once, got %d", purgeCalls)
		}
		if svc.createFromIntentCalls != 1 {
			t.Fatalf("expected create called once, got %d", svc.createFromIntentCalls)
		}
	})
}

func TestFileManagerFileHandlerCreateDULImage_WithoutDULUUID(t *testing.T) {
	actorID := uuidv7.MustBytes()
	uploadIntentID := uuidv7.MustBytes()
	createdID := uuidv7.MustBytes()

	svc := &fileManagerFileHandlerServiceMock{
		createFromIntentFn: func(_ context.Context, params filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
			if params.FolderProvided {
				t.Fatalf("expected FolderProvided=false when dul_uuid omitted")
			}
			if params.Name != "shared image.jpeg" {
				t.Fatalf("unexpected name: %s", params.Name)
			}
			return &filemanager.File{
				ID:        createdID,
				Name:      params.Name,
				ObjectKey: "objects/shared-image.jpeg",
				Status:    filemanager.FileStatusUploaded,
			}, nil
		},
	}

	h := NewFileManagerFileHandler(svc, nil)
	app := newFileManagerFileHandlerTestApp(h, actorID)

	body := []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"name":"shared image.jpeg","upload_intent_uuid":"` + mustUUIDStringFileManager(t, uploadIntentID) + `"}}]}`)
	resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-dul-image", body)
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("expected 201, got %d body=%s", resp.StatusCode, string(respBody))
	}

	var out responsedto.FileManagerFileBulkResponse
	if err := json.Unmarshal(respBody, &out); err != nil {
		t.Fatalf("unmarshal response: %v", err)
	}
	if len(out.Data) != 1 || !out.Data[0].Success {
		t.Fatalf("expected one successful item, got %+v", out.Data)
	}
	if svc.createFromIntentCalls != 1 {
		t.Fatalf("expected createFromIntent called once, got %d", svc.createFromIntentCalls)
	}
}

func TestFileManagerFileHandlerBaseImageFlow(t *testing.T) {
	t.Run("upload base image rejects non-array and empty array payload", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, uuidv7.MustBytes())

		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-base-image", []byte(`{"data":{"type":"file_manager_file_upload_intent","attributes":{"name":"a.png","content_type":"image/png","size_bytes":1}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 non-array payload, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-base-image", []byte(`{"data":[]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 empty array, got %d", resp.StatusCode)
		}
	})

	t.Run("upload base image validates image content type", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, uuidv7.MustBytes())

		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-base-image", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"base.pdf","content_type":"application/pdf","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid base image content type, got %d", resp.StatusCode)
		}
	})

	t.Run("upload base image validates type, name, content type, and size", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, uuidv7.MustBytes())

		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-base-image", []byte(`{"data":[{"type":"wrong","attributes":{"name":"a.png","content_type":"image/png","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 wrong type, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-base-image", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"","content_type":"image/png","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 missing name, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-base-image", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"a.png","content_type":"","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 missing content_type, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-base-image", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"a.png","content_type":"image/png","size_bytes":-1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid size_bytes, got %d", resp.StatusCode)
		}
	})

	t.Run("upload base image success and action error", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		svc := &fileManagerFileHandlerServiceMock{
			requestUploadIntentFn: func(_ context.Context, params filemanager.RequestFileUploadIntentParams) (*filemanager.FileUploadIntentGrant, error) {
				if params.ContentType == "image/bad" {
					return nil, errors.New("upload intent failed")
				}
				return &filemanager.FileUploadIntentGrant{
					UploadIntentID: uuidv7.MustBytes(),
					Name:           params.Name,
					MimeType:       params.ContentType,
					SizeBytes:      params.SizeBytes,
					UploadURL:      "https://upload.example/base",
				}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, actorID)

		okBody := []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"base.png","content_type":"image/png","size_bytes":12}}]}`)
		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-base-image", okBody)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d", resp.StatusCode)
		}

		failBody := []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"base.png","content_type":"image/bad","size_bytes":12}}]}`)
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-base-image", failBody)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 action error, got %d", resp.StatusCode)
		}
	})

	t.Run("create base image resolves forced folder and performs hard replace", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		baseID := uuidv7.MustBytes()
		uploadIntentID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		basesFolderID := uuidv7.MustBytes()
		baseTypeFolderID := uuidv7.MustBytes()
		baseNameFolderID := uuidv7.MustBytes()
		oldFileID := uuidv7.MustBytes()
		newFileID := uuidv7.MustBytes()
		now := time.Now().UTC()

		var deleteCalls int
		var purgeCalls int

		svc := &fileManagerFileHandlerServiceMock{
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID, Name: filemanager.SystemRootFolderName, NameNormalized: filemanager.SystemRootFolderName}, nil
			},
			getFolderByParentFn: func(_ context.Context, parentID []byte, nameNormalized string) (*filemanager.Folder, error) {
				if bytes.Equal(parentID, rootID) && nameNormalized == "bases" {
					return &filemanager.Folder{ID: basesFolderID, ParentID: rootID, Name: "bases", NameNormalized: "bases"}, nil
				}
				if bytes.Equal(parentID, basesFolderID) && nameNormalized == base.CategoryKeyHEMS {
					return &filemanager.Folder{ID: baseTypeFolderID, ParentID: basesFolderID, Name: base.CategoryKeyHEMS, NameNormalized: base.CategoryKeyHEMS}, nil
				}
				if bytes.Equal(parentID, baseTypeFolderID) && nameNormalized == "hems 1" {
					return &filemanager.Folder{ID: baseNameFolderID, ParentID: baseTypeFolderID, Name: "HEMS 1", NameNormalized: "hems 1"}, nil
				}
				return nil, nil
			},
			listFilesByFolderFn: func(_ context.Context, folderID []byte, _ string, _, _ int) ([]filemanager.File, int64, error) {
				if !bytes.Equal(folderID, baseNameFolderID) {
					t.Fatalf("expected list to use base-name folder")
				}
				return []filemanager.File{{ID: oldFileID, FolderID: folderID, Name: "old.png"}}, 1, nil
			},
			deleteFileFn: func(_ context.Context, id []byte, _ []byte, _ *time.Time) error {
				if !bytes.Equal(id, oldFileID) {
					t.Fatalf("expected delete old file id")
				}
				deleteCalls++
				return nil
			},
			purgeFileNodeFn: func(_ context.Context, id []byte) error {
				if !bytes.Equal(id, oldFileID) {
					t.Fatalf("expected purge old file id")
				}
				purgeCalls++
				return nil
			},
			createFromIntentFn: func(_ context.Context, params filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
				if !bytes.Equal(params.FolderID, baseNameFolderID) {
					t.Fatalf("expected create to use base-name folder")
				}
				if !bytes.Equal(params.UploadIntentID, uploadIntentID) {
					t.Fatalf("unexpected upload intent")
				}
				return &filemanager.File{
					ID:             newFileID,
					FolderID:       baseNameFolderID,
					Name:           "new.png",
					NameNormalized: "new.png",
					Extension:      "png",
					SizeBytes:      100,
					MimeType:       "image/png",
					Status:         filemanager.FileStatusUploaded,
					CreatedAt:      now,
					UpdatedAt:      now,
				}, nil
			},
		}
		baseSvc := &fileManagerFileHandlerBaseServiceMock{
			getByIDFn: func(_ context.Context, id []byte, categoryType string) (*base.Base, error) {
				if !bytes.Equal(id, baseID) {
					return nil, nil
				}
				if categoryType == base.CategoryKeyHEMS {
					return &base.Base{ID: id, BaseName: "HEMS 1"}, nil
				}
				return nil, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil).WithBaseService(baseSvc)
		app := newFileManagerFileHandlerTestApp(h, actorID)

		body := []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"` + mustUUIDStringFileManager(t, uploadIntentID) + `","name":"new.png","base_uuid":"` + mustUUIDStringFileManager(t, baseID) + `"}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-base-image", body)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201 base image create, got %d body=%s", resp.StatusCode, string(respBody))
		}
		if deleteCalls != 1 || purgeCalls != 1 {
			t.Fatalf("expected hard replace delete+purge once, got delete=%d purge=%d", deleteCalls, purgeCalls)
		}
	})
}

func TestFileManagerFileHandlerCancelUpload(t *testing.T) {
	t.Run("success cancels upload intent in bulk", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		intentID := uuidv7.MustBytes()

		svc := &fileManagerFileHandlerServiceMock{}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, actorID)

		body := []byte(`{"data":[{"type":"file_manager_file_cancel_upload","attributes":{"upload_intent_uuid":"` + mustUUIDStringFileManager(t, intentID) + `"}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/files/cancel-upload", body)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d body=%s", resp.StatusCode, string(respBody))
		}
		if svc.cancelUploadCalls != 1 {
			t.Fatalf("expected cancel called once, got %d", svc.cancelUploadCalls)
		}
		if svc.lastCancelUpload == nil {
			t.Fatalf("expected cancel params")
		}
		if !bytes.Equal(svc.lastCancelUpload.UploadIntentID, intentID) {
			t.Fatalf("unexpected upload_intent_id")
		}
		if !bytes.Equal(svc.lastCancelUpload.ActorID, actorID) {
			t.Fatalf("unexpected actor_id")
		}

		payload := decodeJSONMapFileManagerFile(t, respBody)
		items := arrayField(t, payload["data"], "data")
		if len(items) != 1 {
			t.Fatalf("expected one item, got %d", len(items))
		}
		item := mapField(t, items[0], "data[0]")
		if v, ok := item["success"].(bool); !ok || !v {
			t.Fatalf("expected success=true, got %#v", item["success"])
		}
		resource := mapField(t, item["resource"], "resource")
		if got := stringField(t, resource, "type"); got != "file_manager_file_cancel_upload_result" {
			t.Fatalf("unexpected resource type %q", got)
		}
		attrs := mapField(t, resource["attributes"], "attributes")
		if got := stringField(t, attrs, "status"); got != "canceled" {
			t.Fatalf("unexpected status %q", got)
		}
	})

	t.Run("invalid item type returns item validation error", func(t *testing.T) {
		svc := &fileManagerFileHandlerServiceMock{}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, nil)

		body := []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"upload_intent_uuid":"` + mustUUIDStringFileManager(t, uuidv7.MustBytes()) + `"}}]}`)
		resp, respBody := doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/files/cancel-upload", body)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d body=%s", resp.StatusCode, string(respBody))
		}
		if svc.cancelUploadCalls != 0 {
			t.Fatalf("cancel should not be called on invalid type")
		}
		payload := decodeJSONMapFileManagerFile(t, respBody)
		items := arrayField(t, payload["data"], "data")
		item := mapField(t, items[0], "data[0]")
		if v, ok := item["success"].(bool); !ok || v {
			t.Fatalf("expected success=false, got %#v", item["success"])
		}
		errObj := mapField(t, item["error"], "error")
		if got := numberField(t, errObj, "status"); got != 422 {
			t.Fatalf("expected status 422, got %.0f", got)
		}
	})
}

type fileManagerDownloadURLStorageMock struct {
	presignedURL string
	presignErr   error
	lastObject   string
	lastTTL      time.Duration
}

func (m *fileManagerDownloadURLStorageMock) EnsureBucket(context.Context) error {
	return nil
}

func (m *fileManagerDownloadURLStorageMock) PutObject(context.Context, filemanager.PutObjectInput) (filemanager.ObjectMetadata, error) {
	return filemanager.ObjectMetadata{}, nil
}

func (m *fileManagerDownloadURLStorageMock) DeleteObject(context.Context, string) error {
	return nil
}

func (m *fileManagerDownloadURLStorageMock) HeadObject(context.Context, string) (filemanager.ObjectMetadata, error) {
	return filemanager.ObjectMetadata{}, nil
}

func (m *fileManagerDownloadURLStorageMock) PresignPutObject(context.Context, filemanager.PresignPutObjectInput) (filemanager.PresignedPutObject, error) {
	return filemanager.PresignedPutObject{}, nil
}

func (m *fileManagerDownloadURLStorageMock) PresignGetObject(_ context.Context, objectKey string, ttl time.Duration) (string, error) {
	m.lastObject = objectKey
	m.lastTTL = ttl
	if m.presignErr != nil {
		return "", m.presignErr
	}
	return m.presignedURL, nil
}

func TestResolveFileManagerDownloadURL(t *testing.T) {
	fileID := uuidv7.MustBytes()
	row := &filemanager.File{
		ID:        fileID,
		ObjectKey: "files/2026/04/12/manual.pdf",
	}

	t.Run("returns presigned url when storage succeeds", func(t *testing.T) {
		storage := &fileManagerDownloadURLStorageMock{
			presignedURL: "https://signed.example/files/manual.pdf?sig=abc",
		}
		got := resolveFileManagerDownloadURL(context.Background(), storage, row)
		if got != storage.presignedURL {
			t.Fatalf("expected presigned url, got %q", got)
		}
		if storage.lastObject != row.ObjectKey {
			t.Fatalf("expected object key %q, got %q", row.ObjectKey, storage.lastObject)
		}
		if storage.lastTTL != fileManagerDownloadURLTTL {
			t.Fatalf("expected ttl %s, got %s", fileManagerDownloadURLTTL, storage.lastTTL)
		}
	})

	t.Run("returns empty when storage nil", func(t *testing.T) {
		got := resolveFileManagerDownloadURL(context.Background(), nil, row)
		if got != "" {
			t.Fatalf("expected empty download_url when storage nil, got %q", got)
		}
	})

	t.Run("returns empty when presign fails", func(t *testing.T) {
		storage := &fileManagerDownloadURLStorageMock{
			presignErr: errors.New("presign failed"),
		}
		got := resolveFileManagerDownloadURL(context.Background(), storage, row)
		if got != "" {
			t.Fatalf("expected empty download_url on presign error, got %q", got)
		}
	})
}

func TestFileManagerFileHandlerReadAndMutateEndpoints(t *testing.T) {
	t.Run("get file success and not found", func(t *testing.T) {
		fileID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		now := time.Now().UTC()
		svc := &fileManagerFileHandlerServiceMock{
			getFileByIDFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
				if bytes.Equal(id, fileID) {
					return &filemanager.File{
						ID:        fileID,
						FolderID:  rootID,
						Name:      "manual.pdf",
						ObjectKey: "files/manual.pdf",
						SizeBytes: 100,
						MimeType:  "application/pdf",
						Status:    filemanager.FileStatusReady,
						CreatedAt: now,
						UpdatedAt: now,
					}, nil
				}
				return nil, nil
			},
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, &fileManagerDownloadURLStorageMock{presignedURL: "https://signed/manual.pdf"})
		app := newFileManagerFileHandlerTestApp(h, nil)

		resp, body := doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/files/get/"+mustUUIDStringFileManager(t, fileID), nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d body=%s", resp.StatusCode, string(body))
		}
		payload := decodeJSONMapFileManagerFile(t, body)
		data := mapField(t, payload["data"], "data")
		attrs := mapField(t, data["attributes"], "attributes")
		if stringField(t, attrs, "download_url") == "" {
			t.Fatalf("expected download_url")
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/files/get/"+mustUUIDStringFileManager(t, uuidv7.MustBytes()), nil)
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404, got %d", resp.StatusCode)
		}
	})

	t.Run("list files and trash files", func(t *testing.T) {
		rootID := uuidv7.MustBytes()
		now := time.Now().UTC()
		trashAt := now.Add(-time.Hour)
		svc := &fileManagerFileHandlerServiceMock{
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID}, nil
			},
			listFilesByFolderFn: func(_ context.Context, folderID []byte, sort string, limit, offset int) ([]filemanager.File, int64, error) {
				return []filemanager.File{{
					ID:        uuidv7.MustBytes(),
					FolderID:  folderID,
					Name:      "a.pdf",
					ObjectKey: "files/a.pdf",
					SizeBytes: 10,
					MimeType:  "application/pdf",
					Status:    filemanager.FileStatusReady,
					CreatedAt: now,
					UpdatedAt: now,
				}}, 1, nil
			},
			listTrashFilesFn: func(_ context.Context, folderID []byte, sort string, limit, offset int) ([]filemanager.File, int64, error) {
				return []filemanager.File{{
					ID:        uuidv7.MustBytes(),
					FolderID:  folderID,
					Name:      "trash.pdf",
					ObjectKey: "files/trash.pdf",
					SizeBytes: 20,
					MimeType:  "application/pdf",
					Status:    filemanager.FileStatusTrashed,
					CreatedAt: now,
					UpdatedAt: now,
					DeletedAt: &trashAt,
				}}, 1, nil
			},
		}
		h := NewFileManagerFileHandler(svc, &fileManagerDownloadURLStorageMock{presignedURL: "https://signed/file.pdf"})
		app := newFileManagerFileHandlerTestApp(h, nil)

		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/files/get-all?page[number]=0&page[size]=999", nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 list files, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodGet, "/api/v1/file-manager/files/trash/get-all?folder_uuid="+mustUUIDStringFileManager(t, rootID), nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 list trash files, got %d", resp.StatusCode)
		}
	})

	t.Run("update delete restore file", func(t *testing.T) {
		fileID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		now := time.Now().UTC()
		svc := &fileManagerFileHandlerServiceMock{
			moveFileFn: func(_ context.Context, id []byte, params filemanager.MoveFileParams) (*filemanager.File, error) {
				return &filemanager.File{
					ID:        id,
					FolderID:  params.FolderID,
					Name:      "renamed.pdf",
					ObjectKey: "files/renamed.pdf",
					MimeType:  "application/pdf",
					Status:    filemanager.FileStatusReady,
					CreatedAt: now,
					UpdatedAt: now,
				}, nil
			},
			deleteFileFn:  func(context.Context, []byte, []byte, *time.Time) error { return nil },
			restoreFileFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFileByIDFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
				return &filemanager.File{
					ID:        id,
					FolderID:  rootID,
					Name:      "renamed.pdf",
					ObjectKey: "files/renamed.pdf",
					MimeType:  "application/pdf",
					Status:    filemanager.FileStatusReady,
					CreatedAt: now,
					UpdatedAt: now,
				}, nil
			},
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID}, nil
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, uuidv7.MustBytes())

		updateBody := []byte(`{"data":{"type":"file_manager_file","id":"` + mustUUIDStringFileManager(t, fileID) + `","attributes":{"name":"renamed.pdf"}}}`)
		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/files/update/"+mustUUIDStringFileManager(t, fileID), 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/files/delete/"+mustUUIDStringFileManager(t, fileID), nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 delete, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPatch, "/api/v1/file-manager/files/restore/"+mustUUIDStringFileManager(t, fileID), nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 restore, got %d", resp.StatusCode)
		}
	})
}

func TestFileManagerFileHandlerHelpers(t *testing.T) {
	t.Run("detail and list resources", func(t *testing.T) {
		fileID := uuidv7.MustBytes()
		folderID := uuidv7.MustBytes()
		now := time.Now().UTC()
		processingAt := now.Add(-time.Minute)
		row := &filemanager.File{
			ID:                  fileID,
			FolderID:            folderID,
			Name:                "manual.pdf",
			NameNormalized:      "manual.pdf",
			Extension:           "pdf",
			SizeBytes:           55,
			MimeType:            "application/pdf",
			ObjectKey:           "files/manual.pdf",
			Status:              filemanager.FileStatusFailed,
			FailureReason:       "failed",
			ProcessingStartedAt: &processingAt,
			CreatedAt:           now,
			UpdatedAt:           now,
		}
		storage := &fileManagerDownloadURLStorageMock{presignedURL: "https://signed/manual.pdf"}
		detail := fileManagerFileDetailResource(context.Background(), storage, row, false)
		if detail.Attributes.Location == nil || detail.Attributes.Location.FolderID == "" {
			t.Fatalf("expected location in detail resource")
		}
		if detail.Attributes.Processing == nil || detail.Attributes.Processing.FailureReason == "" {
			t.Fatalf("expected processing block")
		}
		list := fileManagerFileListResource(context.Background(), storage, row)
		if list.Attributes.DownloadURL == "" {
			t.Fatalf("expected list download url")
		}
	})

	t.Run("upload intent resource and header sanitization", func(t *testing.T) {
		grant := &filemanager.FileUploadIntentGrant{
			UploadIntentID: uuidv7.MustBytes(),
			Bucket:         "bucket",
			ObjectKey:      "objects/key",
			Name:           "manual.pdf",
			MimeType:       "application/pdf",
			SizeBytes:      20,
			Method:         "put",
			UploadURL:      "https://upload",
			Headers: map[string]string{
				"Content-Type":         " application/pdf ",
				"Host":                 "x",
				"Content-Length":       "10",
				"X-Amz-Meta-File_Name": "skip",
				"X-Test":               " ok ",
			},
			ExpiresAt: time.Now().UTC().Add(time.Minute),
		}
		resource := fileManagerFileUploadIntentResource(grant)
		if resource.Attributes.Method != "PUT" {
			t.Fatalf("expected uppercased method")
		}
		if resource.Attributes.RequiredHeaders["content-type"] != "application/pdf" {
			t.Fatalf("expected sanitized content-type header")
		}
		if _, ok := resource.Attributes.RequiredHeaders["host"]; ok {
			t.Fatalf("host header should be removed")
		}
		if fileManagerFileUploadIntentResource(nil).Type != "file_manager_file_upload_intent" {
			t.Fatalf("expected nil resource fallback type")
		}
		if sanitizeUploadRequiredHeaders(map[string]string{"Host": "x"}) != nil {
			t.Fatalf("expected nil sanitized headers when all filtered")
		}
	})

	t.Run("bulk item normalization and helpers", func(t *testing.T) {
		items := []responsedto.FileManagerFileBulkItem{{Index: 0, Success: true}, {Index: 1, Success: false}}
		normalizeFileManagerBulkItems(items)
		if items[0].Success || items[0].Error == nil {
			t.Fatalf("expected missing success resource normalized to error")
		}
		if items[1].Error == nil {
			t.Fatalf("expected missing error normalized")
		}

		uploadItems := []responsedto.FileManagerFileUploadIntentBulkItem{{Index: 0, Success: true}, {Index: 1, Success: false}}
		normalizeFileManagerUploadIntentBulkItems(uploadItems)
		if uploadItems[0].Success || uploadItems[0].Error == nil {
			t.Fatalf("expected upload success item normalized to error when resource missing")
		}
		if uploadItems[1].Error == nil {
			t.Fatalf("expected upload missing error normalized")
		}

		cancelItems := []responsedto.FileManagerFileCancelUploadBulkItem{{Index: 0, Success: true}, {Index: 1, Success: false}}
		normalizeFileManagerCancelUploadBulkItems(cancelItems)
		if cancelItems[0].Success || cancelItems[0].Error == nil {
			t.Fatalf("expected cancel success item normalized to error when resource missing")
		}
		if cancelItems[1].Error == nil {
			t.Fatalf("expected cancel missing error normalized")
		}

		if fileManagerBulkActionErrorItem(0, "Create failed", filemanager.ErrFileNotFound).Error.Status != http.StatusNotFound {
			t.Fatalf("expected bulk action mapped not found status")
		}
		if fileManagerCancelUploadBulkActionErrorItem(0, "Cancel upload failed", filemanager.ErrFileNotFound).Error.Status != http.StatusNotFound {
			t.Fatalf("expected mapped not found status")
		}
	})

	t.Run("uuid param pagination thumbnail and first non empty", func(t *testing.T) {
		app := fiber.New()
		h := NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{}, nil)
		app.Get("/:uuid", func(c *fiber.Ctx) error {
			if _, err := h.parseUUIDParam(c, "uuid", "/path/uuid"); err != nil {
				return err
			}
			page, size, offset := parseFileManagerFilePagination(c)
			if page != 1 || size != 100 || offset != 0 {
				t.Fatalf("unexpected pagination %d %d %d", page, size, offset)
			}
			row := &filemanager.File{ThumbnailObjectKey: ptrString("thumb/key")}
			if got := resolveFileManagerThumbnailDownloadURL(c.Context(), &fileManagerDownloadURLStorageMock{presignedURL: "https://signed/thumb"}, row); got == "" {
				t.Fatalf("expected thumbnail url")
			}
			if got := firstNonEmpty(" ", "b"); got != "b" {
				t.Fatalf("unexpected firstNonEmpty %q", got)
			}
			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 TestFileManagerFileHandlerValidationAndErrorCoverage(t *testing.T) {
	t.Run("create upload cancel list update delete restore validation paths", func(t *testing.T) {
		rootID := uuidv7.MustBytes()
		fileID := uuidv7.MustBytes()
		actorID := uuidv7.MustBytes()
		svc := &fileManagerFileHandlerServiceMock{
			requestUploadIntentFn: func(context.Context, filemanager.RequestFileUploadIntentParams) (*filemanager.FileUploadIntentGrant, error) {
				return nil, filemanager.ErrFileStatusConflict
			},
			cancelUploadFn: func(context.Context, filemanager.CancelFileUploadIntentParams) error {
				return filemanager.ErrFileNotFound
			},
			createFromIntentFn: func(context.Context, filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
				return nil, filemanager.ErrFileStatusConflict
			},
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID}, nil
			},
			listFilesByFolderFn: func(context.Context, []byte, string, int, int) ([]filemanager.File, int64, error) {
				return nil, 0, errors.New("list failure")
			},
			moveFileFn: func(context.Context, []byte, filemanager.MoveFileParams) (*filemanager.File, error) {
				return nil, filemanager.ErrFileNotFound
			},
			deleteFileFn: func(context.Context, []byte, []byte, *time.Time) error {
				return filemanager.ErrFileNotFound
			},
			restoreFileFn: func(context.Context, []byte, []byte, bool, []byte) error {
				return filemanager.ErrFileNotFound
			},
		}
		h := NewFileManagerFileHandler(svc, nil)
		app := newFileManagerFileHandlerTestApp(h, actorID)

		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", []byte(`{`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 invalid create json, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", []byte(`{"data":{"type":"file_manager_file_create"}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create single payload, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", []byte(`{"data":[]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create empty data, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", []byte(`{"data":[{"type":"bad","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uuidv7.MustBytes())+`","name":"a.pdf"}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create invalid type, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"bad","name":"a.pdf"}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create invalid upload uuid, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uuidv7.MustBytes())+`","name":"a.pdf"}}]}`))
		if resp.StatusCode != http.StatusConflict {
			t.Fatalf("expected 409 create action error, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", []byte(`{"data":[]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 upload empty data, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", []byte(`{"data":[{"type":"bad","attributes":{"name":"a.pdf","content_type":"application/pdf","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 upload invalid type, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":" ","content_type":"application/pdf","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 upload missing name, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"a.pdf","content_type":"","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 upload missing content type, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"a.pdf","content_type":"application/pdf","size_bytes":-1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 upload invalid size, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"a.pdf","content_type":"application/pdf","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusConflict {
			t.Fatalf("expected 409 upload action error, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/files/cancel-upload", []byte(`{"data":[]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 cancel empty data, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/files/cancel-upload", []byte(`{"data":[{"type":"bad","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uuidv7.MustBytes())+`"}}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 cancel bulk with item error, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/files/cancel-upload", []byte(`{"data":[{"type":"file_manager_file_cancel_upload","attributes":{"upload_intent_uuid":"bad"}}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 cancel invalid uuid item, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodDelete, "/api/v1/file-manager/files/cancel-upload", []byte(`{"data":[{"type":"file_manager_file_cancel_upload","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uuidv7.MustBytes())+`"}}]}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 cancel action error item, got %d", resp.StatusCode)
		}

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

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

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

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

	t.Run("upload aircraft image and create aircraft image branches", func(t *testing.T) {
		actorID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		aircraftFolderID := uuidv7.MustBytes()
		designationFolderID := uuidv7.MustBytes()
		uploadIntentID := uuidv7.MustBytes()
		aircraftID := uuidv7.MustBytes()
		fileID := uuidv7.MustBytes()
		now := time.Now().UTC()
		folderLookups := 0
		svc := &fileManagerFileHandlerServiceMock{
			requestUploadIntentFn: func(_ context.Context, params filemanager.RequestFileUploadIntentParams) (*filemanager.FileUploadIntentGrant, error) {
				return &filemanager.FileUploadIntentGrant{
					UploadIntentID: uploadIntentID,
					Name:           params.Name,
					MimeType:       params.ContentType,
					SizeBytes:      params.SizeBytes,
					Method:         "put",
					UploadURL:      "https://upload",
					ExpiresAt:      now.Add(time.Minute),
				}, nil
			},
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return &filemanager.Folder{ID: rootID}, nil
			},
			getFolderByParentFn: func(_ context.Context, parentID []byte, normalized string) (*filemanager.Folder, error) {
				folderLookups++
				if bytes.Equal(parentID, rootID) && normalized == aircraftImageFolderName {
					return &filemanager.Folder{ID: aircraftFolderID}, nil
				}
				if bytes.Equal(parentID, aircraftFolderID) && normalized == "aw169" {
					return nil, nil
				}
				if bytes.Equal(parentID, aircraftFolderID) && normalized == "duplicate" {
					if folderLookups%2 == 1 {
						return nil, nil
					}
					return &filemanager.Folder{ID: designationFolderID}, nil
				}
				return nil, nil
			},
			createFolderNodeFn: func(_ context.Context, params filemanager.CreateFolderParams) (*filemanager.Folder, error) {
				if params.Name == "AW169" {
					return &filemanager.Folder{ID: designationFolderID}, nil
				}
				if params.Name == "duplicate" {
					return nil, filemanager.ErrDuplicateName
				}
				return nil, errors.New("create folder failure")
			},
			createFromIntentFn: func(_ context.Context, params filemanager.CreateFileFromUploadIntentParams) (*filemanager.File, error) {
				if params.Name == "conflict.png" {
					return nil, filemanager.ErrFileStatusConflict
				}
				return &filemanager.File{
					ID:        fileID,
					FolderID:  params.FolderID,
					Name:      params.Name,
					ObjectKey: "images/" + params.Name,
					MimeType:  "image/png",
					Status:    filemanager.FileStatusReady,
					CreatedAt: now,
					UpdatedAt: now,
				}, nil
			},
		}
		aircraftSvc := &fileManagerFileHandlerAircraftServiceMock{
			getByIDFn: func(_ context.Context, id []byte) (*helicopter.Helicopter, error) {
				switch {
				case bytes.Equal(id, aircraftID):
					return &helicopter.Helicopter{ID: id, Designation: "AW169"}, nil
				default:
					return nil, nil
				}
			},
		}
		h := NewFileManagerFileHandler(svc, nil).WithAircraftService(aircraftSvc)
		app := newFileManagerFileHandlerTestApp(h, actorID)

		resp, _ := doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"image.png","content_type":"application/pdf","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid aircraft upload content type, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-aircraft-image", []byte(`{`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 invalid aircraft upload json, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-aircraft-image", []byte(`{"data":{"type":"file_manager_file_upload_intent"}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 aircraft upload single payload, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-aircraft-image", []byte(`{"data":[]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 aircraft upload empty data, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-aircraft-image", []byte(`{"data":[{"type":"bad","attributes":{"name":"image.png","content_type":"image/png","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 aircraft upload invalid type, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":" ","content_type":"image/png","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 aircraft upload missing name, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"image.png","content_type":"","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 aircraft upload missing content type, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"image.png","content_type":"image/png","size_bytes":-1}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 aircraft upload invalid size, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/upload-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_upload_intent","attributes":{"name":"image.png","content_type":"image/png","size_bytes":1}}]}`))
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201 aircraft upload success, got %d", resp.StatusCode)
		}

		noAircraftApp := newFileManagerFileHandlerTestApp(NewFileManagerFileHandler(svc, nil), actorID)
		resp, _ = doJSONRequestFileManagerFile(t, noAircraftApp, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"image.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusServiceUnavailable {
			t.Fatalf("expected 503 when aircraft service missing, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"image.png","aircraft_uuid":"bad"}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 invalid aircraft uuid, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 invalid create aircraft image json, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":{"type":"file_manager_file_create"}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create aircraft image single payload, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create aircraft image empty data, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"bad","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"image.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create aircraft image invalid type, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"bad-uuid","name":"image.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create aircraft image invalid upload intent, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":" ","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 create aircraft image missing name, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"image.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, uuidv7.MustBytes())+`"}}]}`))
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404 aircraft not found, got %d", resp.StatusCode)
		}
		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"image.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201 create aircraft image success, got %d", resp.StatusCode)
		}

		aircraftSvcDuplicate := &fileManagerFileHandlerAircraftServiceMock{
			getByIDFn: func(_ context.Context, id []byte) (*helicopter.Helicopter, error) {
				if bytes.Equal(id, aircraftID) {
					return &helicopter.Helicopter{ID: id, Designation: "duplicate"}, nil
				}
				return nil, nil
			},
		}
		hDuplicate := NewFileManagerFileHandler(svc, nil).WithAircraftService(aircraftSvcDuplicate)
		appDuplicate := newFileManagerFileHandlerTestApp(hDuplicate, actorID)
		resp, _ = doJSONRequestFileManagerFile(t, appDuplicate, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"image.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201 duplicate folder recovery success, got %d", resp.StatusCode)
		}

		resp, _ = doJSONRequestFileManagerFile(t, app, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"conflict.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusConflict {
			t.Fatalf("expected 409 create aircraft image action error, got %d", resp.StatusCode)
		}

		errorAircraftSvc := &fileManagerFileHandlerAircraftServiceMock{
			getByIDFn: func(context.Context, []byte) (*helicopter.Helicopter, error) {
				return nil, errors.New("aircraft lookup failure")
			},
		}
		appAircraftErr := newFileManagerFileHandlerTestApp(NewFileManagerFileHandler(svc, nil).WithAircraftService(errorAircraftSvc), actorID)
		resp, _ = doJSONRequestFileManagerFile(t, appAircraftErr, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"image.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 aircraft lookup error, got %d", resp.StatusCode)
		}

		emptyDesignationSvc := &fileManagerFileHandlerAircraftServiceMock{
			getByIDFn: func(_ context.Context, id []byte) (*helicopter.Helicopter, error) {
				return &helicopter.Helicopter{ID: id, Designation: " "}, nil
			},
		}
		appEmptyDesignation := newFileManagerFileHandlerTestApp(NewFileManagerFileHandler(svc, nil).WithAircraftService(emptyDesignationSvc), actorID)
		resp, _ = doJSONRequestFileManagerFile(t, appEmptyDesignation, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"image.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 empty aircraft designation, got %d", resp.StatusCode)
		}

		svcResolveErr := &fileManagerFileHandlerServiceMock{
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return nil, errors.New("root lookup failure")
			},
		}
		appResolveErr := newFileManagerFileHandlerTestApp(NewFileManagerFileHandler(svcResolveErr, nil).WithAircraftService(aircraftSvc), actorID)
		resp, _ = doJSONRequestFileManagerFile(t, appResolveErr, http.MethodPost, "/api/v1/file-manager/files/create-aircraft-image", []byte(`{"data":[{"type":"file_manager_file_create","attributes":{"upload_intent_uuid":"`+mustUUIDStringFileManager(t, uploadIntentID)+`","name":"image.png","aircraft_uuid":"`+mustUUIDStringFileManager(t, aircraftID)+`"}}]}`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 aircraft designation folder resolve error, got %d", resp.StatusCode)
		}
	})

	t.Run("folder helper edge cases", func(t *testing.T) {
		parentID := uuidv7.MustBytes()
		actorID := uuidv7.MustBytes()
		existingID := uuidv7.MustBytes()
		createID := uuidv7.MustBytes()
		h := NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			getFolderByParentFn: func(_ context.Context, _ []byte, normalized string) (*filemanager.Folder, error) {
				switch normalized {
				case "existing":
					return &filemanager.Folder{ID: existingID}, nil
				case "lookup-fail":
					return nil, errors.New("lookup fail")
				default:
					return nil, nil
				}
			},
			createFolderNodeFn: func(_ context.Context, params filemanager.CreateFolderParams) (*filemanager.Folder, error) {
				switch params.Name {
				case "Created":
					return &filemanager.Folder{ID: createID}, nil
				case "broken":
					return nil, errors.New("broken")
				default:
					return nil, nil
				}
			},
		}, nil)

		if _, err := h.ensureFolderByParentAndName(context.Background(), parentID, " ", actorID); !errors.Is(err, filemanager.ErrInvalidName) {
			t.Fatalf("expected invalid name error, got %v", err)
		}
		if got, err := h.ensureFolderByParentAndName(context.Background(), parentID, "existing", actorID); err != nil || !bytes.Equal(got, existingID) {
			t.Fatalf("expected existing folder id, got %x err=%v", got, err)
		}
		if got, err := h.ensureFolderByParentAndName(context.Background(), parentID, "Created", actorID); err != nil || !bytes.Equal(got, createID) {
			t.Fatalf("expected created folder id, got %x err=%v", got, err)
		}
		if _, err := h.ensureFolderByParentAndName(context.Background(), parentID, "lookup-fail", actorID); err == nil {
			t.Fatalf("expected lookup error")
		}
		if _, err := h.ensureFolderByParentAndName(context.Background(), parentID, "broken", actorID); err == nil {
			t.Fatalf("expected create error")
		}

		hNoRoot := NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return nil, nil
			},
		}, nil)
		if _, err := hNoRoot.ensureAircraftDesignationFolder(context.Background(), actorID, "AW169"); !errors.Is(err, filemanager.ErrFolderNotFound) {
			t.Fatalf("expected root not found, got %v", err)
		}

		hRootErr := NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) {
				return nil, errors.New("root fail")
			},
		}, nil)
		if _, err := hRootErr.ensureAircraftDesignationFolder(context.Background(), actorID, "AW169"); err == nil {
			t.Fatalf("expected root lookup error")
		}

		hDuplicateMissing := NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			getFolderByParentFn: func(_ context.Context, _ []byte, normalized string) (*filemanager.Folder, error) {
				if normalized == "dup-missing" {
					return nil, nil
				}
				return nil, nil
			},
			createFolderNodeFn: func(_ context.Context, params filemanager.CreateFolderParams) (*filemanager.Folder, error) {
				if params.Name == "dup-missing" {
					return nil, filemanager.ErrDuplicateName
				}
				return nil, nil
			},
		}, nil)
		if _, err := hDuplicateMissing.ensureFolderByParentAndName(context.Background(), parentID, "dup-missing", actorID); !errors.Is(err, filemanager.ErrFolderNotFound) {
			t.Fatalf("expected duplicate follow-up not found, got %v", err)
		}

		hDuplicateLookupErr := NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			getFolderByParentFn: func(_ context.Context, _ []byte, normalized string) (*filemanager.Folder, error) {
				if normalized == "dup-err" {
					return nil, errors.New("lookup err")
				}
				return nil, nil
			},
			createFolderNodeFn: func(_ context.Context, params filemanager.CreateFolderParams) (*filemanager.Folder, error) {
				if params.Name == "dup-err" {
					return nil, filemanager.ErrDuplicateName
				}
				return nil, nil
			},
		}, nil)
		if _, err := hDuplicateLookupErr.ensureFolderByParentAndName(context.Background(), parentID, "dup-err", actorID); err == nil {
			t.Fatalf("expected duplicate follow-up lookup error")
		}

		hCreateNil := NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			getFolderByParentFn: func(context.Context, []byte, string) (*filemanager.Folder, error) { return nil, nil },
			createFolderNodeFn: func(context.Context, filemanager.CreateFolderParams) (*filemanager.Folder, error) {
				return nil, nil
			},
		}, nil)
		gotNil, err := hCreateNil.ensureFolderByParentAndName(context.Background(), parentID, "nil-create", actorID)
		if err != nil || gotNil != nil {
			t.Fatalf("expected nil folder id and nil error when create returns nil,nil; got %x err=%v", gotNil, err)
		}

		hDuplicateInvalidID := NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			getFolderByParentFn: func(_ context.Context, _ []byte, normalized string) (*filemanager.Folder, error) {
				if normalized == "dup-short" {
					return &filemanager.Folder{ID: []byte("short")}, nil
				}
				return nil, nil
			},
			createFolderNodeFn: func(_ context.Context, params filemanager.CreateFolderParams) (*filemanager.Folder, error) {
				if params.Name == "dup-short" {
					return nil, filemanager.ErrDuplicateName
				}
				return nil, nil
			},
		}, nil)
		if _, err := hDuplicateInvalidID.ensureFolderByParentAndName(context.Background(), parentID, "dup-short", actorID); !errors.Is(err, filemanager.ErrFolderNotFound) {
			t.Fatalf("expected duplicate follow-up invalid id to return folder not found, got %v", err)
		}
	})

	t.Run("restore file remaining branches", func(t *testing.T) {
		fileID := uuidv7.MustBytes()
		rootID := uuidv7.MustBytes()
		nonRootID := uuidv7.MustBytes()
		now := time.Now().UTC()

		appGetErr := newFileManagerFileHandlerTestApp(NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			restoreFileFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFileByIDFn: func(context.Context, []byte) (*filemanager.File, error) { return nil, errors.New("get fail") },
		}, nil), uuidv7.MustBytes())
		resp, _ := doJSONRequestFileManagerFile(t, appGetErr, http.MethodPatch, "/api/v1/file-manager/files/restore/"+mustUUIDStringFileManager(t, fileID), nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 restore get error, got %d", resp.StatusCode)
		}

		appNilRow := newFileManagerFileHandlerTestApp(NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			restoreFileFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFileByIDFn: func(context.Context, []byte) (*filemanager.File, error) { return nil, nil },
		}, nil), uuidv7.MustBytes())
		resp, _ = doJSONRequestFileManagerFile(t, appNilRow, http.MethodPatch, "/api/v1/file-manager/files/restore/"+mustUUIDStringFileManager(t, fileID), nil)
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404 restore missing row, got %d", resp.StatusCode)
		}

		appRootErr := newFileManagerFileHandlerTestApp(NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			restoreFileFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFileByIDFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
				return &filemanager.File{ID: id, FolderID: nonRootID, Name: "a.png", CreatedAt: now, UpdatedAt: now}, nil
			},
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) { return nil, errors.New("root fail") },
		}, nil), uuidv7.MustBytes())
		resp, _ = doJSONRequestFileManagerFile(t, appRootErr, http.MethodPatch, "/api/v1/file-manager/files/restore/"+mustUUIDStringFileManager(t, fileID), nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400 restore root error, got %d", resp.StatusCode)
		}

		appNonRoot := newFileManagerFileHandlerTestApp(NewFileManagerFileHandler(&fileManagerFileHandlerServiceMock{
			restoreFileFn: func(context.Context, []byte, []byte, bool, []byte) error { return nil },
			getFileByIDFn: func(_ context.Context, id []byte) (*filemanager.File, error) {
				return &filemanager.File{ID: id, FolderID: nonRootID, Name: "a.png", CreatedAt: now, UpdatedAt: now}, nil
			},
			getOrCreateRootFn: func(context.Context) (*filemanager.Folder, error) { return &filemanager.Folder{ID: rootID}, nil },
		}, nil), uuidv7.MustBytes())
		resp, body := doJSONRequestFileManagerFile(t, appNonRoot, http.MethodPatch, "/api/v1/file-manager/files/restore/"+mustUUIDStringFileManager(t, fileID), nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200 restore non-root success, got %d", resp.StatusCode)
		}
		out := decodeJSONMapFileManagerFile(t, body)
		attrs := mapField(t, out["data"], "data")["attributes"]
		location := mapField(t, mapField(t, attrs, "attributes")["location"], "location")
		if location["folder_id"] != mustUUIDStringFileManager(t, nonRootID) {
			t.Fatalf("expected non-root folder id in restore response")
		}
	})
}
