import 'server-only'
import { randomUUID } from 'crypto'
import { mkdir, writeFile } from 'fs/promises'
import path from 'path'
import sharp from 'sharp'

const UPLOAD_DIR = process.env.UPLOAD_DIR || path.join(process.cwd(), 'uploads')

// Files are partitioned by year/month so a single directory never grows
// unbounded: <UPLOAD_DIR>/2026/05/<uuid>.<ext>
function datedDir(): { rel: string; abs: string } {
  const now = new Date()
  const rel = path.join(String(now.getUTCFullYear()), String(now.getUTCMonth() + 1).padStart(2, '0'))
  return { rel, abs: path.join(UPLOAD_DIR, rel) }
}

export function absFromStored(stored: string): string {
  // Stored paths are relative to UPLOAD_DIR (e.g. "2026/05/<uuid>.webp").
  return path.join(UPLOAD_DIR, stored)
}

const AUDIO_EXT: Record<string, string> = {
  'audio/webm': 'webm',
  'audio/ogg': 'ogg',
  'audio/mp4': 'm4a',
  'audio/mpeg': 'mp3',
  'audio/wav': 'wav',
  'audio/x-wav': 'wav',
}

function audioExt(mime: string): string {
  const base = mime.split(';')[0].trim().toLowerCase()
  return AUDIO_EXT[base] || 'bin'
}

export async function saveAudio(buf: Buffer, mime: string): Promise<string> {
  const { rel, abs } = datedDir()
  await mkdir(abs, { recursive: true })
  const name = `${randomUUID()}.${audioExt(mime)}`
  await writeFile(path.join(abs, name), buf)
  return path.join(rel, name)
}

// Non-image attachments (PDFs, docs…) are stored as-is, keeping the original
// extension so Content-Type sniffing and downloads behave.
export async function saveRawFile(buf: Buffer, originalName: string): Promise<string> {
  const { rel, abs } = datedDir()
  await mkdir(abs, { recursive: true })
  const ext = path.extname(originalName).slice(0, 10).replace(/[^a-zA-Z0-9.]/g, '') || '.bin'
  const name = `${randomUUID()}${ext.startsWith('.') ? ext : `.${ext}`}`
  await writeFile(path.join(abs, name), buf)
  return path.join(rel, name)
}

export interface SavedImage {
  path: string
  thumbPath: string
  width: number
  height: number
}

// Re-encode to WebP and strip EXIF (sharp.rotate() bakes in orientation then
// drops the metadata). Full image is bounded to a 1200px long edge; thumbnail
// is a 400px attention-cropped square — same recipe eorganize uses.
export async function saveImage(buf: Buffer): Promise<SavedImage> {
  const { rel, abs } = datedDir()
  await mkdir(abs, { recursive: true })
  const id = randomUUID()

  const full = sharp(buf).rotate().resize(1200, 1200, { fit: 'inside', withoutEnlargement: true }).webp({ quality: 80 })
  const fullBuf = await full.toBuffer({ resolveWithObject: true })
  const fullName = `${id}.webp`
  await writeFile(path.join(abs, fullName), fullBuf.data)

  const thumbBuf = await sharp(buf)
    .rotate()
    .resize(400, 400, { fit: 'cover', position: sharp.strategy.attention })
    .webp({ quality: 70 })
    .toBuffer()
  const thumbName = `${id}.thumb.webp`
  await writeFile(path.join(abs, thumbName), thumbBuf)

  return {
    path: path.join(rel, fullName),
    thumbPath: path.join(rel, thumbName),
    width: fullBuf.info.width,
    height: fullBuf.info.height,
  }
}
