/**
 * blog_uploads — upload + serve d'images du blog.
 *
 * Architecture (cf. /docs/tech/architecture.md §3) :
 *  - Upload : POST /api/v1/admin/uploads → multipart → MinIO (drive S3)
 *    sous la clé `uploads/blog/{uuid}.{ext}`, visibilité privée.
 *  - Lecture : GET /api/v1/uploads/blog/:filename → stream depuis MinIO
 *    avec Cache-Control: public, max-age=31536000, immutable. Le fichier
 *    est immuable (uuid dans le nom = chaque upload = nouvelle URL), donc
 *    cache infini sans risque d'invalidation.
 *  - L'URL publique est ensuite stockée dans posts.hero_image_url (et
 *    optionnellement og_image_url) — pas de FK, simple reference texte.
 *
 * Orphelins : quand un post change de hero, l'ancienne image reste sur
 * MinIO (~quelques KB par fichier). Acceptable pour V1 ; périodique
 * cleanup à ajouter quand on dépassera ~100 articles.
 */

import { randomUUID } from 'node:crypto'
import path from 'node:path'

import drive from '@adonisjs/drive/services/main'
import type { MultipartFile } from '@adonisjs/core/bodyparser'

const ALLOWED_EXTS = ['jpg', 'jpeg', 'png', 'webp'] as const
const MAX_BYTES = 4 * 1024 * 1024 // 4 MB

export type UploadResult = {
  /** URL publique relative (à préfixer du host API côté client). */
  publicPath: string
  /** Clé S3 réelle dans le bucket (debug / cleanup). */
  storageKey: string
  /** Type MIME inféré de l'extension. */
  contentType: string
  /** Taille réelle en bytes. */
  sizeBytes: number
}

/**
 * Reçoit un MultipartFile (validé Adonis) et le pousse sur MinIO.
 * Throw si le fichier ne respecte pas les contraintes (taille, MIME).
 */
export async function uploadBlogImage(file: MultipartFile): Promise<UploadResult> {
  const ext = (file.extname ?? '').toLowerCase()
  if (!ALLOWED_EXTS.includes(ext as (typeof ALLOWED_EXTS)[number])) {
    throw new Error(`unsupported_extension: ${ext} (autorisés : ${ALLOWED_EXTS.join(', ')})`)
  }
  if (file.size > MAX_BYTES) {
    throw new Error(`file_too_large: ${file.size}B (max ${MAX_BYTES}B)`)
  }

  const uuid = randomUUID()
  const filename = `${uuid}.${ext}`
  const storageKey = `uploads/blog/${filename}`

  // Adonis Drive accepte un path local (le multipart écrit le tmp file).
  if (!file.tmpPath) {
    throw new Error('multipart_no_tmpPath')
  }
  await drive.use().moveFromFs(file.tmpPath, storageKey)

  return {
    publicPath: `/api/v1/uploads/blog/${filename}`,
    storageKey,
    contentType: extToContentType(ext),
    sizeBytes: file.size,
  }
}

/**
 * Stream une image depuis MinIO. Renvoie un Buffer + le contentType
 * pour que le contrôleur réponde avec les bons headers.
 */
export async function readBlogImage(filename: string): Promise<{
  buffer: Buffer
  contentType: string
} | null> {
  // Sécurité : pas de path traversal. Le slug doit matcher `uuid.ext`.
  if (!/^[a-f0-9-]{36}\.(jpg|jpeg|png|webp)$/i.test(filename)) {
    return null
  }
  const storageKey = `uploads/blog/${filename}`

  try {
    const buffer = Buffer.from(await drive.use().getArrayBuffer(storageKey))
    return {
      buffer,
      contentType: extToContentType(path.extname(filename).slice(1).toLowerCase()),
    }
  } catch {
    return null
  }
}

function extToContentType(ext: string): string {
  switch (ext) {
    case 'jpg':
    case 'jpeg':
      return 'image/jpeg'
    case 'png':
      return 'image/png'
    case 'webp':
      return 'image/webp'
    default:
      return 'application/octet-stream'
  }
}
