import { TRPCError } from '@trpc/server'
import type { PrismaClient } from '@prisma/client'
import { logAudit } from './audit'
import {
  getStorageProviderWithType,
  createStorageProvider,
  getContentType,
  isValidImageType,
  type StorageProviderType,
} from '@/lib/storage'

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

/**
 * Configuration for an image upload domain (avatar, logo, etc.)
 *
 * Each config describes how to read/write image keys for a specific entity.
 */
export type ImageUploadConfig<TSelectResult> = {
  /** Human-readable label used in log/error messages (e.g. "avatar", "logo") */
  label: string

  /** Generate a storage object key for a new upload */
  generateKey: (entityId: string, fileName: string) => string

  /** Prisma select – fetch the current image key + provider for the entity */
  findCurrent: (
    prisma: PrismaClient,
    entityId: string
  ) => Promise<TSelectResult | null>

  /** Extract the image key from the select result */
  getImageKey: (record: TSelectResult) => string | null

  /** Extract the storage provider type from the select result */
  getProviderType: (record: TSelectResult) => StorageProviderType

  /** Prisma update – set the new image key + provider on the entity */
  setImage: (
    prisma: PrismaClient,
    entityId: string,
    key: string,
    providerType: StorageProviderType
  ) => Promise<unknown>

  /** Prisma update – clear the image key + provider on the entity */
  clearImage: (prisma: PrismaClient, entityId: string) => Promise<unknown>

  /** Audit log entity type (e.g. "User", "Project") */
  auditEntityType: string

  /** Audit log field name (e.g. "profileImageKey", "logoKey") */
  auditFieldName: string
}

type AuditContext = {
  userId: string
  ip: string
  userAgent: string
}

// ---------------------------------------------------------------------------
// Shared operations
// ---------------------------------------------------------------------------

/**
 * Get a pre-signed upload URL for an image.
 *
 * Validates the content type, generates a storage key, and returns the
 * upload URL along with the key and provider type.
 */
export async function getImageUploadUrl(
  entityId: string,
  fileName: string,
  contentType: string,
  generateKey: (entityId: string, fileName: string) => string
): Promise<{ uploadUrl: string; key: string; providerType: StorageProviderType }> {
  if (!isValidImageType(contentType)) {
    throw new TRPCError({
      code: 'BAD_REQUEST',
      message: `Invalid image type: "${contentType}". Allowed: JPEG, PNG, GIF, WebP, SVG`,
    })
  }

  const key = generateKey(entityId, fileName)
  const resolvedContentType = getContentType(fileName)

  const { provider, providerType } = await getStorageProviderWithType()
  const uploadUrl = await provider.getUploadUrl(key, resolvedContentType)

  return { uploadUrl, key, providerType }
}

/**
 * Confirm an image upload: verify the object exists in storage, delete the
 * previous image (if any), persist the new key, and write an audit log entry.
 */
export async function confirmImageUpload<TSelectResult>(
  prisma: PrismaClient,
  config: ImageUploadConfig<TSelectResult>,
  entityId: string,
  key: string,
  providerType: StorageProviderType,
  audit: AuditContext
): Promise<void> {
  // 1. Verify upload exists in storage
  const provider = createStorageProvider(providerType)
  const exists = await provider.objectExists(key)
  if (!exists) {
    throw new TRPCError({
      code: 'NOT_FOUND',
      message: 'Upload not found. Please try uploading again.',
    })
  }

  // 2. Delete old image if present
  const current = await config.findCurrent(prisma, entityId)
  if (current) {
    const oldKey = config.getImageKey(current)
    if (oldKey) {
      try {
        const oldProvider = createStorageProvider(config.getProviderType(current))
        await oldProvider.deleteObject(oldKey)
      } catch (error) {
        console.warn(`Failed to delete old ${config.label}:`, error)
      }
    }
  }

  // 3. Persist new image key + provider
  await config.setImage(prisma, entityId, key, providerType)

  // 4. Audit log
  await logAudit({
    prisma,
    userId: audit.userId,
    action: 'UPDATE',
    entityType: config.auditEntityType,
    entityId,
    detailsJson: {
      field: config.auditFieldName,
      newValue: key,
      provider: providerType,
    },
    ipAddress: audit.ip,
    userAgent: audit.userAgent,
  })
}

/**
 * Get the download URL for an existing image, or null if none is set.
 */
export async function getImageUrl<TSelectResult>(
  prisma: PrismaClient,
  config: Pick<ImageUploadConfig<TSelectResult>, 'findCurrent' | 'getImageKey' | 'getProviderType'>,
  entityId: string
): Promise<string | null> {
  const record = await config.findCurrent(prisma, entityId)
  if (!record) return null

  const imageKey = config.getImageKey(record)
  if (!imageKey) return null

  const providerType = config.getProviderType(record)
  const provider = createStorageProvider(providerType)
  return provider.getDownloadUrl(imageKey)
}

/**
 * Delete an image from storage and clear the reference in the database.
 * Writes an audit log entry.
 */
export async function deleteImage<TSelectResult>(
  prisma: PrismaClient,
  config: ImageUploadConfig<TSelectResult>,
  entityId: string,
  audit: AuditContext
): Promise<{ success: true }> {
  const record = await config.findCurrent(prisma, entityId)
  if (!record) return { success: true }

  const imageKey = config.getImageKey(record)
  if (!imageKey) return { success: true }

  // Delete from storage
  const providerType = config.getProviderType(record)
  const provider = createStorageProvider(providerType)
  try {
    await provider.deleteObject(imageKey)
  } catch (error) {
    console.warn(`Failed to delete ${config.label} from storage:`, error)
  }

  // Clear in database
  await config.clearImage(prisma, entityId)

  // Audit log
  await logAudit({
    prisma,
    userId: audit.userId,
    action: 'DELETE',
    entityType: config.auditEntityType,
    entityId,
    detailsJson: { field: config.auditFieldName },
    ipAddress: audit.ip,
    userAgent: audit.userAgent,
  })

  return { success: true }
}
