import crypto from 'node:crypto'
import { prismaClient } from './prisma.mjs'

function sha256(buffer) {
  if (!buffer?.length) return ''
  return crypto.createHash('sha256').update(buffer).digest('hex')
}

export function normalizeUploadedFileOwnerType(ownerType = '') {
  const value = String(ownerType || '').trim()
  if (value === 'expert_withdrawal_proof') return 'withdrawal_proof'
  return value
}

function normalizeFileInput(file = {}, defaults = {}) {
  const buffer = Buffer.isBuffer(file.buffer) ? file.buffer : null
  const contentBase64 = file.contentBase64 ?? ''
  const storagePath = String(file.storagePath || '')
  const bucket = String(file.bucket || file.storageBucket || (file.storageDriver === 's3' && storagePath.includes('/') ? storagePath.split('/')[0] : '') || '')
  const fileName = String(file.fileName || defaults.fileName || 'download').trim() || 'download'
  const mimeType = String(file.mimeType || defaults.mimeType || 'application/octet-stream').trim() || 'application/octet-stream'
  const fileSize = Number(file.fileSize ?? file.size ?? buffer?.length ?? 0) || 0
  return {
    fileName,
    mimeType,
    fileSize,
    contentBase64,
    storageDriver: String(file.storageDriver || defaults.storageDriver || (storagePath ? 'local' : 'database')).trim() || 'database',
    objectKey: String(file.objectKey || '').trim(),
    storagePath,
    bucket,
    checksum: String(file.checksum || sha256(buffer)).trim(),
    visibility: String(file.visibility || defaults.visibility || 'private').trim(),
    ownerType: normalizeUploadedFileOwnerType(file.ownerType || defaults.ownerType || ''),
    ownerId: String(file.ownerId || defaults.ownerId || '').trim(),
  }
}

export function uploadedFileJson(file) {
  if (!file) return null
  return {
    id: file.id,
    fileName: file.fileName || '',
    mimeType: file.mimeType || 'application/octet-stream',
    fileSize: file.fileSize || 0,
    contentBase64: file.contentBase64 || '',
    storageDriver: file.storageDriver || 'database',
    objectKey: file.objectKey || '',
    storagePath: file.storagePath || '',
    bucket: file.bucket || '',
    checksum: file.checksum || '',
    visibility: file.visibility || 'private',
    ownerType: normalizeUploadedFileOwnerType(file.ownerType || ''),
    ownerId: file.ownerId || '',
    url: `/api/files/${file.id}`,
    createdAt: file.createdAt,
  }
}

export async function createUploadedFileRecord(file = {}, defaults = {}) {
  const prisma = prismaClient()
  const data = normalizeFileInput(file, defaults)
  const created = await prisma.uploadedFile.create({ data })
  return uploadedFileJson(created)
}

export async function getUploadedFileRecord(fileId) {
  const prisma = prismaClient()
  const id = String(fileId || '').trim()
  if (!id) return null
  const file = await prisma.uploadedFile.findFirst({ where: { id } })
  return uploadedFileJson(file)
}

export async function canUserReadUploadedFile(file, userId) {
  if (!file || !userId) return false
  if (file.visibility === 'public') return true
  const ownerType = normalizeUploadedFileOwnerType(file.ownerType)
  if (!ownerType) return false
  if (file.ownerId && file.ownerId === userId) return true

  const prisma = prismaClient()
  if (ownerType === 'invoice_application') {
    const invoice = await prisma.invoiceApplication.findFirst({
      where: {
        id: file.ownerId,
        order: { is: { userId } },
      },
      select: { id: true },
    })
    return Boolean(invoice)
  }

  if (ownerType === 'data_service_deliverable') {
    const rows = await prisma.$queryRaw`
      select exists (
        select 1
        from data_service_requests dsr
        where dsr.user_id = ${userId}::uuid
          and (
            dsr.deliverable_file_id = ${file.id}::uuid
            or ${file.id}::uuid = any(dsr.deliverable_file_ids)
            or dsr.final_file_id = ${file.id}::uuid
          )
      ) as ok
    `
    return Boolean(rows?.[0]?.ok)
  }

  if (ownerType === 'data_service_request') {
    const rows = await prisma.$queryRaw`
      select exists (
        select 1
        from data_service_requests dsr
        where dsr.user_id = ${userId}::uuid
          and (
            dsr.id::text = ${file.ownerId}
            or dsr.id::text = ${file.id}
          )
      ) as ok
    `
    return Boolean(rows?.[0]?.ok)
  }

  if (ownerType === 'consulting_deliverable') {
    const rows = await prisma.$queryRaw`
      select exists (
        select 1
        from consulting_requests cr
        where cr.user_id = ${userId}::uuid
          and (
            cr.id::text = ${file.ownerId}
            or cr.final_file_id = ${file.id}::uuid
          )
      ) as ok
    `
    return Boolean(rows?.[0]?.ok)
  }

  if (ownerType === 'expert_material') {
    const rows = await prisma.$queryRaw`
      select exists (
        select 1
        from expert_applications ea
        where ea.user_id = ${userId}::uuid
          and (
            ea.id::text = ${file.ownerId}
            or ea.photo_file_id = ${file.id}::uuid
            or ${file.id}::uuid = any(ea.attachment_file_ids)
          )
        union all
        select 1
        from expert_bookings eb
        where eb.user_id = ${userId}::uuid
          and (
            eb.id::text = ${file.ownerId}
            or ${file.id}::uuid = any(eb.material_file_ids)
          )
        union all
        select 1
        from expert_bookings eb
        join expert_profiles e on e.id = eb.expert_id
        where e.user_id = ${userId}::uuid
          and ${file.id}::uuid = any(eb.material_file_ids)
        limit 1
      ) as ok
    `
    return Boolean(rows?.[0]?.ok)
  }

  if (ownerType === 'withdrawal_proof') {
    const rows = await prisma.$queryRaw`
      select exists (
        select 1
        from expert_withdrawals w
        where w.user_id = ${userId}::uuid
          and w.payment_proof_file_id = ${file.id}::uuid
      ) as ok
    `
    return Boolean(rows?.[0]?.ok)
  }

  return false
}

export async function canAdminReadUploadedFile(file, hasPermission) {
  if (!file) return false
  if (file.visibility === 'public') return true
  const ownerType = normalizeUploadedFileOwnerType(file.ownerType)
  if (!ownerType) return false
  const permissionByOwner = {
    invoice_application: 'invoice:read',
    consulting_deliverable: 'consulting:read',
    data_service_deliverable: 'data_service:read',
    data_service_request: 'data_service:read',
    withdrawal_proof: 'settlement:read',
    expert_material: ['expert_application:read', 'expert_booking:read'],
    expert_avatar: 'expert_profile:read',
    product_asset: 'product:read',
    cms_asset: 'site:read',
    site_asset: 'site:read',
    user_upload: 'user:read',
  }
  const required = permissionByOwner[ownerType] || ''
  if (!required) return hasPermission('*')
  const requiredPermissions = Array.isArray(required) ? required : [required]
  return requiredPermissions.some((permission) => hasPermission(permission)) || hasPermission('*')
}

export async function markUploadedFilesOwner(fileIds = [], {
  ownerType = '',
  ownerId = '',
  visibility = 'private',
  currentOwnerTypes = [],
  currentOwnerId = '',
  allowUnowned = false,
} = {}) {
  const ids = fileIds.map((id) => String(id || '').trim()).filter(Boolean)
  if (!ids.length) return
  const prisma = prismaClient()
  const normalizedOwnerType = normalizeUploadedFileOwnerType(ownerType)
  const normalizedCurrentOwnerTypes = (Array.isArray(currentOwnerTypes) ? currentOwnerTypes : [currentOwnerTypes])
    .map((type) => normalizeUploadedFileOwnerType(type))
    .filter(Boolean)
  if (!normalizedCurrentOwnerTypes.length && !currentOwnerId && !allowUnowned) {
    const error = new Error('文件改绑缺少当前 owner 约束')
    error.statusCode = 500
    error.code = 'uploaded_file_owner_guard_required'
    throw error
  }
  const allowedOwnerTypeFilter = normalizedCurrentOwnerTypes.length
    ? { ownerType: { in: normalizedCurrentOwnerTypes } }
    : {}
  const ownerGuards = []
  if (currentOwnerId) ownerGuards.push({ ownerId: String(currentOwnerId) })
  if (allowUnowned) ownerGuards.push({ ownerId: '' })
  const where = {
    id: { in: ids },
    ...allowedOwnerTypeFilter,
    ...(ownerGuards.length ? { OR: ownerGuards } : {}),
  }
  const result = await prisma.uploadedFile.updateMany({
    where,
    data: {
      ownerType: normalizedOwnerType,
      ownerId: String(ownerId || ''),
      visibility,
    },
  })
  if (result.count !== ids.length) {
    const error = new Error('无权使用或改绑该文件')
    error.statusCode = 403
    error.code = 'uploaded_file_owner_forbidden'
    throw error
  }
}

export async function markDataServiceDeliverableFiles(fileIds = []) {
  return markUploadedFilesOwner(fileIds, {
    ownerType: 'data_service_deliverable',
    visibility: 'private',
    currentOwnerTypes: ['data_service_deliverable'],
    allowUnowned: true,
  })
}
