/**
 * Tests for file presigned API route
 *
 * @vitest-environment node
 */

import { authMockFns, storageServiceMock, storageServiceMockFns } from '@sim/testing'
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

const {
  mockVerifyFileAccess,
  mockVerifyWorkspaceFileAccess,
  mockUseBlobStorage,
  mockUseS3Storage,
  mockGetStorageConfig,
  mockIsUsingCloudStorage,
  mockGetStorageProvider,
  mockValidateFileType,
  mockValidateAttachmentFileType,
  mockGenerateCopilotUploadUrl,
  mockIsImageFileType,
  mockGetStorageProviderUploads,
  mockIsUsingCloudStorageUploads,
  mockGetUserEntityPermissions,
  mockGenerateWorkspaceFileKey,
  mockGenerateExecutionFileKey,
  mockInsertFileMetadata,
} = vi.hoisted(() => ({
  mockVerifyFileAccess: vi.fn().mockResolvedValue(true),
  mockVerifyWorkspaceFileAccess: vi.fn().mockResolvedValue(true),
  mockUseBlobStorage: { value: false },
  mockUseS3Storage: { value: true },
  mockGetStorageConfig: vi.fn(),
  mockIsUsingCloudStorage: vi.fn(),
  mockGetStorageProvider: vi.fn(),
  mockValidateFileType: vi.fn().mockReturnValue(null),
  mockValidateAttachmentFileType: vi.fn().mockReturnValue(null),
  mockGenerateCopilotUploadUrl: vi.fn().mockResolvedValue({
    url: 'https://example.com/presigned-url',
    key: 'copilot/test-key.txt',
  }),
  mockIsImageFileType: vi.fn().mockReturnValue(true),
  mockGetStorageProviderUploads: vi.fn(),
  mockIsUsingCloudStorageUploads: vi.fn(),
  mockGetUserEntityPermissions: vi.fn().mockResolvedValue('admin'),
  mockGenerateWorkspaceFileKey: vi.fn(
    (workspaceId: string, fileName: string) => `workspace/${workspaceId}/${fileName}`
  ),
  mockGenerateExecutionFileKey: vi.fn(
    (ctx: { workspaceId: string; workflowId: string; executionId: string }, fileName: string) =>
      `execution/${ctx.workspaceId}/${ctx.workflowId}/${ctx.executionId}/${fileName}`
  ),
  mockInsertFileMetadata: vi.fn().mockResolvedValue({ id: 'wf_test' }),
}))

vi.mock('@/app/api/files/authorization', () => ({
  verifyFileAccess: mockVerifyFileAccess,
  verifyWorkspaceFileAccess: mockVerifyWorkspaceFileAccess,
}))

vi.mock('@/lib/uploads/config', () => ({
  get USE_BLOB_STORAGE() {
    return mockUseBlobStorage.value
  },
  get USE_S3_STORAGE() {
    return mockUseS3Storage.value
  },
  UPLOAD_DIR: '/uploads',
  getStorageConfig: mockGetStorageConfig,
  isUsingCloudStorage: mockIsUsingCloudStorage,
  getStorageProvider: mockGetStorageProvider,
}))

vi.mock('@/lib/uploads/core/storage-service', () => storageServiceMock)

vi.mock('@/lib/uploads/utils/validation', () => ({
  validateFileType: mockValidateFileType,
  validateAttachmentFileType: mockValidateAttachmentFileType,
}))

vi.mock('@/lib/workspaces/permissions/utils', () => ({
  getUserEntityPermissions: mockGetUserEntityPermissions,
}))

vi.mock('@/lib/uploads/contexts/workspace/workspace-file-manager', () => ({
  generateWorkspaceFileKey: mockGenerateWorkspaceFileKey,
}))

vi.mock('@/lib/uploads/contexts/execution/utils', () => ({
  generateExecutionFileKey: mockGenerateExecutionFileKey,
}))

vi.mock('@/lib/uploads/server/metadata', () => ({
  insertFileMetadata: mockInsertFileMetadata,
  recordKnowledgeBaseFileOwnership: (ownership: Record<string, unknown>) =>
    mockInsertFileMetadata({ ...ownership, context: 'knowledge-base' }),
}))

vi.mock('@/lib/uploads/utils/file-utils', () => ({
  isImageFileType: mockIsImageFileType,
}))

vi.mock('@/lib/uploads', () => ({
  CopilotFiles: {
    generateCopilotUploadUrl: mockGenerateCopilotUploadUrl,
  },
  getStorageProvider: mockGetStorageProviderUploads,
  isUsingCloudStorage: mockIsUsingCloudStorageUploads,
}))

import { POST } from '@/app/api/files/presigned/route'

const defaultMockUser = {
  id: 'test-user-id',
  name: 'Test User',
  email: 'test@example.com',
}

function setupFileApiMocks(
  options: {
    authenticated?: boolean
    storageProvider?: 's3' | 'blob' | 'local'
    cloudEnabled?: boolean
  } = {}
) {
  const { authenticated = true, storageProvider = 's3', cloudEnabled = true } = options

  if (authenticated) {
    authMockFns.mockGetSession.mockResolvedValue({ user: defaultMockUser })
  } else {
    authMockFns.mockGetSession.mockResolvedValue(null)
  }

  const useBlobStorage = storageProvider === 'blob' && cloudEnabled
  const useS3Storage = storageProvider === 's3' && cloudEnabled

  mockUseBlobStorage.value = useBlobStorage
  mockUseS3Storage.value = useS3Storage

  mockGetStorageConfig.mockReturnValue(
    useBlobStorage
      ? {
          accountName: 'testaccount',
          accountKey: 'testkey',
          connectionString: 'testconnection',
          containerName: 'testcontainer',
        }
      : {
          bucket: 'test-bucket',
          region: 'us-east-1',
        }
  )
  mockIsUsingCloudStorage.mockReturnValue(cloudEnabled)
  mockGetStorageProvider.mockReturnValue(
    storageProvider === 'blob' ? 'Azure Blob' : storageProvider === 's3' ? 'S3' : 'Local'
  )

  storageServiceMockFns.mockHasCloudStorage.mockReturnValue(cloudEnabled)
  storageServiceMockFns.mockGeneratePresignedUploadUrl.mockImplementation(
    async (opts: { fileName: string; context: string; customKey?: string }) => {
      const timestamp = Date.now()
      const safeFileName = opts.fileName.replace(/[^a-zA-Z0-9.-]/g, '_')
      const key = opts.customKey ?? `${opts.context}/${timestamp}-ik3a6w4-${safeFileName}`
      return {
        url: 'https://example.com/presigned-url',
        key,
      }
    }
  )
  storageServiceMockFns.mockGeneratePresignedDownloadUrl.mockResolvedValue(
    'https://example.com/presigned-url'
  )

  mockValidateFileType.mockReturnValue(null)
  mockValidateAttachmentFileType.mockReturnValue(null)
  mockGetUserEntityPermissions.mockResolvedValue('admin')

  mockGetStorageProviderUploads.mockReturnValue(
    storageProvider === 'blob' ? 'Azure Blob' : storageProvider === 's3' ? 'S3' : 'Local'
  )
  mockIsUsingCloudStorageUploads.mockReturnValue(cloudEnabled)
}

describe('/api/files/presigned', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.useFakeTimers()
    vi.setSystemTime(new Date('2024-01-01T00:00:00Z'))

    vi.stubGlobal('crypto', {
      randomUUID: vi.fn().mockReturnValue('mock-uuid-1234-5678'),
    })
  })

  afterEach(() => {
    vi.useRealTimers()
  })

  describe('POST', () => {
    it('should return graceful fallback response when cloud storage is not enabled', async () => {
      setupFileApiMocks({
        cloudEnabled: false,
        storageProvider: 's3',
      })

      const request = new NextRequest('http://localhost:3000/api/files/presigned?type=chat', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'test.txt',
          contentType: 'text/plain',
          fileSize: 1024,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(data.directUploadSupported).toBe(false)
      expect(data.presignedUrl).toBe('')
      expect(data.fileName).toBe('test.txt')
      expect(data.fileInfo).toBeDefined()
      expect(data.fileInfo.name).toBe('test.txt')
      expect(data.fileInfo.size).toBe(1024)
      expect(data.fileInfo.type).toBe('text/plain')
    })

    it('should return error when fileName is missing', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      const request = new NextRequest('http://localhost:3000/api/files/presigned', {
        method: 'POST',
        body: JSON.stringify({
          contentType: 'text/plain',
          fileSize: 1024,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(400)
      expect(data.error).toBe('fileName is required and cannot be empty')
      expect(data.code).toBe('VALIDATION_ERROR')
    })

    it('should return error when contentType is missing', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      const request = new NextRequest('http://localhost:3000/api/files/presigned', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'test.txt',
          fileSize: 1024,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(400)
      expect(data.error).toBe('contentType is required and cannot be empty')
      expect(data.code).toBe('VALIDATION_ERROR')
    })

    it('should return error when fileSize is invalid', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      const request = new NextRequest('http://localhost:3000/api/files/presigned', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'test.txt',
          contentType: 'text/plain',
          fileSize: 0,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(400)
      expect(data.error).toBe('fileSize must be a positive number')
      expect(data.code).toBe('VALIDATION_ERROR')
    })

    it('should return error when file size exceeds limit', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      const largeFileSize = 150 * 1024 * 1024 // 150MB (exceeds 100MB limit)
      const request = new NextRequest('http://localhost:3000/api/files/presigned', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'large-file.txt',
          contentType: 'text/plain',
          fileSize: largeFileSize,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(400)
      expect(data.error).toContain('exceeds maximum allowed size')
      expect(data.code).toBe('VALIDATION_ERROR')
    })

    it('should generate S3 presigned URL successfully', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      const request = new NextRequest('http://localhost:3000/api/files/presigned?type=chat', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'test document.txt',
          contentType: 'text/plain',
          fileSize: 1024,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(data.presignedUrl).toBe('https://example.com/presigned-url')
      expect(data.fileInfo).toMatchObject({
        path: expect.stringMatching(/\/api\/files\/serve\/s3\/.+\?context=chat$/),
        key: expect.stringMatching(/.*test.document\.txt$/),
        name: 'test document.txt',
        size: 1024,
        type: 'text/plain',
      })
      expect(data.directUploadSupported).toBe(true)
    })

    it('should generate knowledge-base S3 presigned URL with kb prefix', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=knowledge-base&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'knowledge-doc.pdf',
            contentType: 'application/pdf',
            fileSize: 2048,
          }),
        }
      )

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(data.fileInfo.key).toMatch(/^kb\/.*knowledge-doc\.pdf$/)
      expect(data.directUploadSupported).toBe(true)
    })

    it('should generate chat S3 presigned URL with chat prefix and direct path', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      const request = new NextRequest('http://localhost:3000/api/files/presigned?type=chat', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'chat-logo.png',
          contentType: 'image/png',
          fileSize: 4096,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(data.fileInfo.key).toMatch(/^chat\/.*chat-logo\.png$/)
      expect(data.fileInfo.path).toMatch(/\/api\/files\/serve\/s3\/.+\?context=chat$/)
      expect(data.presignedUrl).toBeTruthy()
      expect(data.directUploadSupported).toBe(true)
    })

    it('should generate Azure Blob presigned URL successfully', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 'blob',
      })

      const request = new NextRequest('http://localhost:3000/api/files/presigned?type=chat', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'test document.txt',
          contentType: 'text/plain',
          fileSize: 1024,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(data.presignedUrl).toBeTruthy()
      expect(typeof data.presignedUrl).toBe('string')
      expect(data.fileInfo).toMatchObject({
        key: expect.stringMatching(/.*test.document\.txt$/),
        name: 'test document.txt',
        size: 1024,
        type: 'text/plain',
      })
      expect(data.directUploadSupported).toBe(true)
    })

    it('should generate chat Azure Blob presigned URL with chat prefix and direct path', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 'blob',
      })

      const request = new NextRequest('http://localhost:3000/api/files/presigned?type=chat', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'chat-logo.png',
          contentType: 'image/png',
          fileSize: 4096,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(data.fileInfo.key).toMatch(/^chat\/.*chat-logo\.png$/)
      expect(data.fileInfo.path).toMatch(/\/api\/files\/serve\/blob\/.+\?context=chat$/)
      expect(data.presignedUrl).toBeTruthy()
      expect(data.directUploadSupported).toBe(true)
    })

    it('should return error for unknown storage provider', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      storageServiceMockFns.mockGeneratePresignedUploadUrl.mockRejectedValue(
        new Error('Unknown storage provider: unknown')
      )

      const request = new NextRequest('http://localhost:3000/api/files/presigned?type=chat', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'test.txt',
          contentType: 'text/plain',
          fileSize: 1024,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(500)
      expect(data.error).toBeTruthy()
      expect(typeof data.error).toBe('string')
    })

    it('should handle S3 errors gracefully', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      storageServiceMockFns.mockGeneratePresignedUploadUrl.mockRejectedValue(
        new Error('S3 service unavailable')
      )

      const request = new NextRequest('http://localhost:3000/api/files/presigned?type=chat', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'test.txt',
          contentType: 'text/plain',
          fileSize: 1024,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(500)
      expect(data.error).toBeTruthy()
      expect(typeof data.error).toBe('string')
    })

    it('should handle Azure Blob errors gracefully', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 'blob',
      })

      storageServiceMockFns.mockGeneratePresignedUploadUrl.mockRejectedValue(
        new Error('Azure service unavailable')
      )

      const request = new NextRequest('http://localhost:3000/api/files/presigned?type=chat', {
        method: 'POST',
        body: JSON.stringify({
          fileName: 'test.txt',
          contentType: 'text/plain',
          fileSize: 1024,
        }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(500)
      expect(data.error).toBeTruthy()
      expect(typeof data.error).toBe('string')
    })

    it('should handle malformed JSON gracefully', async () => {
      setupFileApiMocks({
        cloudEnabled: true,
        storageProvider: 's3',
      })

      const request = new NextRequest('http://localhost:3000/api/files/presigned', {
        method: 'POST',
        body: 'invalid json',
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(400) // Changed from 500 to 400 (ValidationError)
      expect(data.error).toBe('Invalid JSON in request body') // Updated error message
      expect(data.code).toBe('VALIDATION_ERROR')
    })
  })

  describe('mothership uploads', () => {
    it('uses validateAttachmentFileType (not validateFileType) — accepts images', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'screenshot.png',
            contentType: 'image/png',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      expect(response.status).toBe(200)
      expect(mockValidateAttachmentFileType).toHaveBeenCalledWith('screenshot.png')
      expect(mockValidateFileType).not.toHaveBeenCalled()
    })

    it('rejects unsupported types when validator returns an error', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
      mockValidateAttachmentFileType.mockReturnValue({
        code: 'UNSUPPORTED_FILE_TYPE',
        message: 'Unsupported file type: exe.',
        supportedTypes: [],
      })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'virus.exe',
            contentType: 'application/octet-stream',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      const data = await response.json()
      expect(response.status).toBe(400)
      expect(data.code).toBe('VALIDATION_ERROR')
      expect(data.error).toContain('exe')
    })

    it('returns 403 when user lacks workspace write permission', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
      mockGetUserEntityPermissions.mockResolvedValue('read')

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'doc.pdf',
            contentType: 'application/pdf',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      expect(response.status).toBe(403)
    })

    it('inserts a workspaceFiles row with context=mothership so previews authorize', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'screenshot.png',
            contentType: 'image/png',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(mockInsertFileMetadata).toHaveBeenCalledTimes(1)
      expect(mockInsertFileMetadata).toHaveBeenCalledWith({
        key: data.fileInfo.key,
        userId: 'test-user-id',
        workspaceId: 'ws-1',
        context: 'mothership',
        originalName: 'screenshot.png',
        contentType: 'image/png',
        size: 4096,
      })
    })

    it('returns 500 when insertFileMetadata fails so callers do not get an unauthorizable URL', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
      mockInsertFileMetadata.mockRejectedValueOnce(new Error('DB connection lost'))

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'screenshot.png',
            contentType: 'image/png',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      expect(response.status).toBe(500)
    })
  })

  describe('execution uploads', () => {
    it('uses validateAttachmentFileType — accepts video', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=execution&workspaceId=ws-1&workflowId=wf-1&executionId=exec-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'output.mp4',
            contentType: 'video/mp4',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      expect(response.status).toBe(200)
      expect(mockValidateAttachmentFileType).toHaveBeenCalledWith('output.mp4')
      expect(mockValidateFileType).not.toHaveBeenCalled()
    })

    it('rejects when validator returns an error', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
      mockValidateAttachmentFileType.mockReturnValue({
        code: 'UNSUPPORTED_FILE_TYPE',
        message: 'Unsupported file type: bin.',
        supportedTypes: [],
      })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=execution&workspaceId=ws-1&workflowId=wf-1&executionId=exec-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'blob.bin',
            contentType: 'application/octet-stream',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      const data = await response.json()
      expect(response.status).toBe(400)
      expect(data.code).toBe('VALIDATION_ERROR')
    })

    it('returns 400 when missing workflowId/executionId', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=execution&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'output.mp4',
            contentType: 'video/mp4',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      expect(response.status).toBe(400)
    })

    it('inserts a workspaceFiles row with context=execution so previews authorize', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=execution&workspaceId=ws-1&workflowId=wf-1&executionId=exec-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'output.mp4',
            contentType: 'video/mp4',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(mockInsertFileMetadata).toHaveBeenCalledTimes(1)
      expect(mockInsertFileMetadata).toHaveBeenCalledWith({
        key: data.fileInfo.key,
        userId: 'test-user-id',
        workspaceId: 'ws-1',
        context: 'execution',
        originalName: 'output.mp4',
        contentType: 'video/mp4',
        size: 4096,
      })
    })
  })

  describe('workspace-logos uploads', () => {
    it('inserts a workspaceFiles row with context=workspace-logos so logos authorize', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=workspace-logos&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'logo.png',
            contentType: 'image/png',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(mockInsertFileMetadata).toHaveBeenCalledTimes(1)
      expect(mockInsertFileMetadata).toHaveBeenCalledWith({
        key: data.fileInfo.key,
        userId: 'test-user-id',
        workspaceId: 'ws-1',
        context: 'workspace-logos',
        originalName: 'logo.png',
        contentType: 'image/png',
        size: 4096,
      })
    })
  })

  describe('knowledge-base uploads', () => {
    it('uses validateFileType (docs-only), not validateAttachmentFileType', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=knowledge-base&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'doc.pdf',
            contentType: 'application/pdf',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      expect(response.status).toBe(200)
      expect(mockValidateFileType).toHaveBeenCalledWith('doc.pdf', 'application/pdf')
      expect(mockValidateAttachmentFileType).not.toHaveBeenCalled()
    })

    it('requires workspaceId for knowledge-base uploads', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=knowledge-base',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'doc.pdf',
            contentType: 'application/pdf',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      expect(response.status).toBe(400)
    })

    it('returns 403 when the user lacks write access to the workspace', async () => {
      setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
      mockGetUserEntityPermissions.mockResolvedValue('read')

      const request = new NextRequest(
        'http://localhost:3000/api/files/presigned?type=knowledge-base&workspaceId=ws-1',
        {
          method: 'POST',
          body: JSON.stringify({
            fileName: 'doc.pdf',
            contentType: 'application/pdf',
            fileSize: 4096,
          }),
        }
      )

      const response = await POST(request)
      expect(response.status).toBe(403)
    })
  })
})
