import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { sanitizeFileName } from '@/executor/constants'
import '@/lib/uploads/core/setup.server'
import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
import {
  uploadFilesFormFieldsSchema,
  uploadFilesFormFilesSchema,
} from '@/lib/api/contracts/storage-transfer'
import { getValidationErrorMessage } from '@/lib/api/server'
import { getSession } from '@/lib/auth'
import {
  assertKnownSizeWithinLimit,
  isPayloadSizeLimitError,
  MAX_MULTIPART_OVERHEAD_BYTES,
  readFileToBufferWithLimit,
  readFormDataWithLimit,
} from '@/lib/core/utils/stream-limits'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { captureServerEvent } from '@/lib/posthog/server'
import type { StorageContext } from '@/lib/uploads/config'
import { generateKnowledgeBaseFileKey } from '@/lib/uploads/contexts/knowledge-base/knowledge-base-file-manager'
import { generateWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
import { MAX_WORKSPACE_FORMDATA_FILE_SIZE } from '@/lib/uploads/shared/types'
import { isImageFileType, resolveFileType } from '@/lib/uploads/utils/file-utils'
import {
  SUPPORTED_ATTACHMENT_EXTENSIONS,
  SUPPORTED_IMAGE_EXTENSIONS,
  validateFileType,
} from '@/lib/uploads/utils/validation'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
import { createErrorResponse, InvalidRequestError } from '@/app/api/files/utils'

const ALLOWED_EXTENSIONS = new Set<string>(SUPPORTED_ATTACHMENT_EXTENSIONS)

function validateFileExtension(filename: string): boolean {
  const extension = filename.split('.').pop()?.toLowerCase()
  if (!extension) return false
  return ALLOWED_EXTENSIONS.has(extension)
}

export const dynamic = 'force-dynamic'

const logger = createLogger('FilesUploadAPI')

export const POST = withRouteHandler(async (request: NextRequest) => {
  try {
    const session = await getSession()
    if (!session?.user?.id) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    const formData = await readFormDataWithLimit(request, {
      maxBytes: MAX_WORKSPACE_FORMDATA_FILE_SIZE + MAX_MULTIPART_OVERHEAD_BYTES,
      label: 'multipart upload body',
    })

    const rawFiles = formData.getAll('file')
    const filesResult = uploadFilesFormFilesSchema.safeParse(rawFiles)
    if (!filesResult.success) {
      throw new InvalidRequestError('No files provided')
    }
    const files = filesResult.data
    const totalFileSize = files.reduce((total, file) => total + file.size, 0)
    assertKnownSizeWithinLimit(totalFileSize, MAX_WORKSPACE_FORMDATA_FILE_SIZE, 'uploaded files')

    const formFieldsResult = uploadFilesFormFieldsSchema.safeParse({
      workflowId: formData.get('workflowId'),
      executionId: formData.get('executionId'),
      workspaceId: formData.get('workspaceId'),
      context: formData.get('context'),
    })
    if (!formFieldsResult.success) {
      throw new InvalidRequestError(
        getValidationErrorMessage(formFieldsResult.error, 'Invalid upload form data')
      )
    }
    const formFields = formFieldsResult.data
    const { workflowId, executionId, workspaceId, context: contextParam } = formFields

    // Context must be explicitly provided
    if (!contextParam) {
      throw new InvalidRequestError(
        'Upload requires explicit context parameter (knowledge-base, workspace, execution, copilot, chat, profile-pictures, or workspace-logos)'
      )
    }

    const context = contextParam as StorageContext

    const storageService = await import('@/lib/uploads/core/storage-service')
    const usingCloudStorage = storageService.hasCloudStorage()
    logger.info(`Using storage mode: ${usingCloudStorage ? 'Cloud' : 'Local'} for file upload`)

    // Execution context requires a workspace write/admin permission check. Resolve it once per
    // request (not per file) since workspaceId is invariant across all files in the upload.
    let executionUploadContext:
      | { workspaceId: string; workflowId: string; executionId: string }
      | undefined
    if (context === 'execution') {
      if (!workflowId || !executionId || !workspaceId) {
        throw new InvalidRequestError(
          'Execution context requires workflowId, executionId, and workspaceId parameters'
        )
      }

      const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId)
      if (permission !== 'write' && permission !== 'admin') {
        return NextResponse.json(
          { error: 'Write or Admin access required for execution uploads' },
          { status: 403 }
        )
      }

      executionUploadContext = { workspaceId, workflowId, executionId }
    }

    // Mothership context requires the same workspace write/admin permission check, plus a
    // storage quota check. Resolve both once per request (not per file) since workspaceId is
    // invariant across all files in the upload and quota must account for the full batch size,
    // not just one file.
    let mothershipWorkspaceId: string | undefined
    if (context === 'mothership') {
      if (!workspaceId) {
        throw new InvalidRequestError('Mothership context requires workspaceId parameter')
      }

      const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId)
      if (permission !== 'write' && permission !== 'admin') {
        return NextResponse.json(
          { error: 'Write or Admin access required for mothership uploads' },
          { status: 403 }
        )
      }

      const { checkStorageQuota } = await import('@/lib/billing/storage')
      const quotaCheck = await checkStorageQuota(session.user.id, totalFileSize)
      if (!quotaCheck.allowed) {
        return NextResponse.json(
          { error: quotaCheck.error || 'Storage limit exceeded' },
          { status: 413 }
        )
      }

      mothershipWorkspaceId = workspaceId
    }

    const uploadResults = []

    for (const file of files) {
      const originalName = file.name || 'untitled.md'

      if (!validateFileExtension(originalName)) {
        const extension = originalName.split('.').pop()?.toLowerCase() || 'unknown'
        throw new InvalidRequestError(
          `File type '${extension}' is not allowed. Allowed types: ${Array.from(ALLOWED_EXTENSIONS).join(', ')}`
        )
      }

      const buffer = await readFileToBufferWithLimit(file, {
        maxBytes: MAX_WORKSPACE_FORMDATA_FILE_SIZE,
        label: 'uploaded file',
      })

      // Handle execution context
      if (context === 'execution' && executionUploadContext) {
        const { uploadExecutionFile } = await import('@/lib/uploads/contexts/execution')
        const userFile = await uploadExecutionFile(
          executionUploadContext,
          buffer,
          originalName,
          file.type,
          session.user.id
        )

        uploadResults.push(userFile)
        continue
      }

      // Handle knowledge-base context
      if (context === 'knowledge-base') {
        // Validate file type for knowledge base
        const validationError = validateFileType(originalName, file.type)
        if (validationError) {
          throw new InvalidRequestError(validationError.message)
        }

        if (!workspaceId) {
          throw new InvalidRequestError('workspaceId is required for knowledge-base uploads')
        }

        const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId)
        if (permission !== 'write' && permission !== 'admin') {
          return NextResponse.json(
            { error: 'Write or Admin access required for knowledge-base uploads' },
            { status: 403 }
          )
        }

        logger.info(`Uploading knowledge-base file: ${originalName}`)

        const storageKey = generateKnowledgeBaseFileKey(originalName)

        const metadata: Record<string, string> = {
          originalName: originalName,
          uploadedAt: new Date().toISOString(),
          purpose: 'knowledge-base',
          userId: session.user.id,
          workspaceId,
        }

        const fileInfo = await storageService.uploadFile({
          file: buffer,
          fileName: storageKey,
          contentType: file.type,
          context: 'knowledge-base',
          preserveKey: true,
          customKey: storageKey,
          metadata,
        })

        const finalPath = usingCloudStorage
          ? `${fileInfo.path}?context=knowledge-base`
          : fileInfo.path

        const uploadResult = {
          fileName: originalName,
          presignedUrl: '', // Not used for server-side uploads
          fileInfo: {
            path: finalPath,
            key: fileInfo.key,
            name: originalName,
            size: buffer.length,
            type: file.type,
          },
          directUploadSupported: false,
        }

        logger.info(`Successfully uploaded knowledge-base file: ${fileInfo.key}`)
        uploadResults.push(uploadResult)
        continue
      }

      // Handle workspace context
      if (context === 'workspace') {
        if (!workspaceId) {
          throw new InvalidRequestError('Workspace context requires workspaceId parameter')
        }
        const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId)
        if (permission !== 'admin' && permission !== 'write') {
          return NextResponse.json(
            { error: 'Write or Admin access required for workspace uploads' },
            { status: 403 }
          )
        }

        try {
          const { uploadWorkspaceFile } = await import('@/lib/uploads/contexts/workspace')
          const userFile = await uploadWorkspaceFile(
            workspaceId,
            session.user.id,
            buffer,
            originalName,
            file.type || 'application/octet-stream'
          )

          uploadResults.push(userFile)
          continue
        } catch (workspaceError) {
          const errorMessage = getErrorMessage(workspaceError, 'Upload failed')
          const isDuplicate = errorMessage.includes('already exists')
          const isStorageLimitError =
            errorMessage.includes('Storage limit exceeded') ||
            errorMessage.includes('storage limit')

          logger.warn(`Workspace file upload failed: ${errorMessage}`)

          let statusCode = 500
          if (isDuplicate) statusCode = 409
          else if (isStorageLimitError) statusCode = 413

          return NextResponse.json(
            {
              success: false,
              error: errorMessage,
              isDuplicate,
            },
            { status: statusCode }
          )
        }
      }

      // Handle mothership context (chat-scoped uploads to workspace S3)
      if (context === 'mothership' && mothershipWorkspaceId) {
        logger.info(`Uploading mothership file: ${originalName}`)

        const storageKey = generateWorkspaceFileKey(mothershipWorkspaceId, originalName)

        const metadata: Record<string, string> = {
          originalName: originalName,
          uploadedAt: new Date().toISOString(),
          purpose: 'mothership',
          userId: session.user.id,
          workspaceId: mothershipWorkspaceId,
        }

        const fileInfo = await storageService.uploadFile({
          file: buffer,
          fileName: storageKey,
          contentType: file.type || 'application/octet-stream',
          context: 'mothership',
          preserveKey: true,
          customKey: storageKey,
          metadata,
        })

        const finalPath = usingCloudStorage ? `${fileInfo.path}?context=mothership` : fileInfo.path

        uploadResults.push({
          fileName: originalName,
          presignedUrl: '',
          fileInfo: {
            path: finalPath,
            key: fileInfo.key,
            name: originalName,
            size: buffer.length,
            type: file.type || 'application/octet-stream',
          },
          directUploadSupported: false,
        })

        logger.info(`Successfully uploaded mothership file: ${fileInfo.key}`)
        continue
      }

      if (
        context === 'copilot' ||
        context === 'chat' ||
        context === 'profile-pictures' ||
        context === 'workspace-logos'
      ) {
        if (context !== 'copilot') {
          const mimeType = file.type
          const isGenericMime = !mimeType || mimeType === 'application/octet-stream'
          const extension = originalName.split('.').pop()?.toLowerCase() ?? ''
          const extensionIsImage = (SUPPORTED_IMAGE_EXTENSIONS as readonly string[]).includes(
            extension
          )
          const isImage = isGenericMime ? extensionIsImage : isImageFileType(mimeType)
          if (!isImage) {
            throw new InvalidRequestError(`Only image files are allowed for ${context} uploads`)
          }
        }

        if (context === 'workspace-logos') {
          if (!workspaceId) {
            throw new InvalidRequestError('workspace-logos context requires workspaceId parameter')
          }
          const permission = await getUserEntityPermissions(
            session.user.id,
            'workspace',
            workspaceId
          )
          if (permission !== 'admin') {
            return NextResponse.json(
              { error: 'Admin access required for workspace logo uploads' },
              { status: 403 }
            )
          }
        }

        if (context === 'chat' && workspaceId) {
          const permission = await getUserEntityPermissions(
            session.user.id,
            'workspace',
            workspaceId
          )
          if (permission === null) {
            return NextResponse.json(
              { error: 'Insufficient permissions for workspace' },
              { status: 403 }
            )
          }
        }

        logger.info(`Uploading ${context} file: ${originalName}`)

        const resolvedContentType = resolveFileType({ type: file.type, name: originalName })

        const timestamp = Date.now()
        const safeFileName = sanitizeFileName(originalName)
        const storageKey = `${context}/${timestamp}-${safeFileName}`

        const metadata: Record<string, string> = {
          originalName: originalName,
          uploadedAt: new Date().toISOString(),
          purpose: context,
          userId: session.user.id,
        }

        if (workspaceId && context === 'chat') {
          metadata.workspaceId = workspaceId
        }

        const fileInfo = await storageService.uploadFile({
          file: buffer,
          fileName: storageKey,
          contentType: resolvedContentType,
          context,
          preserveKey: true,
          customKey: storageKey,
          metadata,
        })

        const finalPath = usingCloudStorage ? `${fileInfo.path}?context=${context}` : fileInfo.path

        const uploadResult = {
          fileName: originalName,
          presignedUrl: '', // Not used for server-side uploads
          fileInfo: {
            path: finalPath,
            key: fileInfo.key,
            name: originalName,
            size: buffer.length,
            type: resolvedContentType,
          },
          directUploadSupported: false,
        }

        logger.info(`Successfully uploaded ${context} file: ${fileInfo.key}`)

        if (context === 'workspace-logos' && workspaceId) {
          recordAudit({
            workspaceId,
            actorId: session.user.id,
            actorName: session.user.name,
            actorEmail: session.user.email,
            action: AuditAction.FILE_UPLOADED,
            resourceType: AuditResourceType.WORKSPACE,
            resourceId: workspaceId,
            description: `Uploaded workspace logo "${originalName}"`,
            metadata: {
              fileName: originalName,
              fileKey: fileInfo.key,
              fileSize: buffer.length,
              fileType: resolvedContentType,
            },
            request,
          })

          captureServerEvent(session.user.id, 'workspace_logo_uploaded', {
            workspace_id: workspaceId,
            file_name: originalName,
            file_size: buffer.length,
          })
        }

        uploadResults.push(uploadResult)
        continue
      }

      // Unknown context
      throw new InvalidRequestError(
        `Unsupported context: ${context}. Use knowledge-base, workspace, execution, copilot, chat, profile-pictures, or workspace-logos`
      )
    }

    if (uploadResults.length === 1) {
      return NextResponse.json(uploadResults[0])
    }
    return NextResponse.json({ files: uploadResults })
  } catch (error) {
    logger.error('Error in file upload:', error)
    if (isPayloadSizeLimitError(error)) {
      return NextResponse.json(
        {
          error: 'PayloadSizeLimitError',
          message: `File exceeds the server upload limit of ${Math.round(error.maxBytes / (1024 * 1024))}MB. Use direct upload for larger workspace files.`,
        },
        { status: 413 }
      )
    }
    return createErrorResponse(error instanceof Error ? error : new Error('File upload failed'))
  }
})
