import * as Minio from 'minio'

// MinIO client singletons (lazy-initialized to avoid build-time errors)
const globalForMinio = globalThis as unknown as {
  minio: Minio.Client | undefined
  minioPublic: Minio.Client | undefined
}

// Internal endpoint for server-to-server communication
const MINIO_ENDPOINT = process.env.MINIO_ENDPOINT || 'http://localhost:9000'

// Public endpoint for browser-accessible URLs (pre-signed URLs)
// If not set, falls back to internal endpoint
export const MINIO_PUBLIC_ENDPOINT = process.env.MINIO_PUBLIC_ENDPOINT || MINIO_ENDPOINT

function createClientFromUrl(endpoint: string): Minio.Client {
  const url = new URL(endpoint)

  const accessKey = process.env.MINIO_ACCESS_KEY
  const secretKey = process.env.MINIO_SECRET_KEY
  if (process.env.NODE_ENV === 'production' && (!accessKey || !secretKey)) {
    throw new Error('MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables are required in production')
  }

  return new Minio.Client({
    endPoint: url.hostname,
    port: url.port ? parseInt(url.port) : (url.protocol === 'https:' ? 443 : 80),
    useSSL: url.protocol === 'https:',
    accessKey: accessKey || 'minioadmin',
    secretKey: secretKey || 'minioadmin',
  })
}

/**
 * Get the internal MinIO client (for server-side operations: bucket ops, delete, etc.)
 */
export function getMinioClient(): Minio.Client {
  if (!globalForMinio.minio) {
    globalForMinio.minio = createClientFromUrl(MINIO_ENDPOINT)
  }
  return globalForMinio.minio
}

/**
 * Get the public MinIO client (for generating presigned URLs).
 * Uses MINIO_PUBLIC_ENDPOINT so the AWS V4 signature matches the Host
 * header the browser will send. Without this, presigned URLs generated
 * against the internal hostname fail with SignatureDoesNotMatch.
 */
function getPublicMinioClient(): Minio.Client {
  if (!globalForMinio.minioPublic) {
    globalForMinio.minioPublic = createClientFromUrl(MINIO_PUBLIC_ENDPOINT)
  }
  return globalForMinio.minioPublic
}

// Backward-compatible export — lazy getter via Proxy (internal client)
export const minio: Minio.Client = new Proxy({} as Minio.Client, {
  get(_target, prop, receiver) {
    return Reflect.get(getMinioClient(), prop, receiver)
  },
})

// Default bucket name
export const BUCKET_NAME = process.env.MINIO_BUCKET || 'mopc-files'

// =============================================================================
// Helper Functions
// =============================================================================

/**
 * Generate a pre-signed URL for file download or upload.
 * Uses the public MinIO client so the AWS V4 signature is computed against
 * the public hostname — the browser's Host header will match the signature.
 */
export async function getPresignedUrl(
  bucket: string,
  objectKey: string,
  method: 'GET' | 'PUT' = 'GET',
  expirySeconds: number = 900, // 15 minutes default
  options?: { downloadFileName?: string; inline?: boolean; contentType?: string }
): Promise<string> {
  const publicClient = getPublicMinioClient()
  if (method === 'GET') {
    let respHeaders: Record<string, string> | undefined
    if (options?.inline) {
      respHeaders = { 'response-content-disposition': 'inline' }
      if (options.contentType) respHeaders['response-content-type'] = options.contentType
    } else if (options?.downloadFileName) {
      respHeaders = { 'response-content-disposition': `attachment; filename="${options.downloadFileName}"` }
    }
    return publicClient.presignedGetObject(bucket, objectKey, expirySeconds, respHeaders)
  } else {
    return publicClient.presignedPutObject(bucket, objectKey, expirySeconds)
  }
}

/**
 * Check if a bucket exists, create if not
 */
export async function ensureBucket(bucket: string): Promise<void> {
  const exists = await minio.bucketExists(bucket)
  if (!exists) {
    await minio.makeBucket(bucket)
    console.log(`Created MinIO bucket: ${bucket}`)
  }
}

/**
 * Delete an object from MinIO
 */
export async function deleteObject(
  bucket: string,
  objectKey: string
): Promise<void> {
  await minio.removeObject(bucket, objectKey)
}

/**
 * Sanitize a name for use as a MinIO path segment.
 * Removes special characters, replaces spaces with underscores, limits length.
 */
export function sanitizePath(name: string): string {
  return (
    name
      .trim()
      .replace(/[^a-zA-Z0-9\-_ ]/g, '')
      .replace(/\s+/g, '_')
      .substring(0, 100) || 'unnamed'
  )
}

/**
 * Generate a unique object key for a project file.
 *
 * Structure: {ProjectName}/{RoundName}/{timestamp}-{fileName}
 * - projectName: human-readable project title (sanitized)
 * - roundName: round name for submission context (sanitized), defaults to "general"
 * - fileName: original file name (sanitized)
 *
 * Existing files with old-style keys (projects/{id}/...) are unaffected
 * because retrieval uses the objectKey stored in the database.
 */
export function generateObjectKey(
  projectName: string,
  fileName: string,
  roundName?: string
): string {
  const timestamp = Date.now()
  const sanitizedProject = sanitizePath(projectName)
  const sanitizedRound = roundName ? sanitizePath(roundName) : 'general'
  const sanitizedFile = fileName.replace(/[^a-zA-Z0-9.-]/g, '_')
  return `${sanitizedProject}/${sanitizedRound}/${timestamp}-${sanitizedFile}`
}

/**
 * Generate a unique object key for a mentor-workspace file.
 *
 * Structure: {ProjectName}/mentorship/{timestamp}-{fileName}
 *
 * Mirrors generateObjectKey but pins the round-name slot to "mentorship"
 * so all mentor workspace files for a project live under one folder.
 */
export function generateMentorObjectKey(
  projectTitle: string,
  fileName: string,
): string {
  return generateObjectKey(projectTitle, fileName, 'mentorship')
}

/**
 * Verify a client-supplied object key actually belongs to a project's
 * sanitized prefix. Used to prevent cross-tenant binding where an attacker
 * passes another team's `bucket+objectKey` into a metadata-save procedure.
 */
export function objectKeyBelongsToProject(
  objectKey: string,
  projectTitle: string,
): boolean {
  const sanitized = sanitizePath(projectTitle)
  return objectKey.startsWith(`${sanitized}/`)
}

