import * as fs from 'fs';
import * as path from 'path';

import { getUploadRoot } from './upload-root';

// Writes an uploaded file to the local disk and returns its public URL.
// The file name is "<timestamp>-<cleaned original name>".
export async function saveFileToDisk(
  file: Express.Multer.File,
  folderName?: string,
): Promise<string> {
  const uploadRoot = getUploadRoot();
  const folderSegment = toSafeFolderSegment(folderName);
  const relativeDir = folderSegment
    ? `${uploadRoot}/${folderSegment}`
    : uploadRoot;

  // Absolute path: project-root/uploads[/folder]
  const uploadDir = path.join(process.cwd(), relativeDir);

  // Create the folder if it does not exist yet.
  await fs.promises.mkdir(uploadDir, { recursive: true });

  const safeOriginalName = toSafeFilename(file.originalname);
  const filename = `${Date.now()}-${safeOriginalName}`;
  const filepath = path.join(uploadDir, filename);

  await fs.promises.writeFile(filepath, file.buffer);

  // This URL works directly in the browser because the app serves
  // the upload folder as static files.
  return `/${relativeDir}/${filename}`;
}

// Deletes a stored file by its public URL.
// Returns true when the file was deleted, false when the URL does not
// point to a real file inside the upload folder.
export async function deleteFileFromDisk(fileUrl: string): Promise<boolean> {
  const targetAbs = getStoredFilePath(fileUrl);
  if (!targetAbs) {
    return false;
  }

  try {
    await fs.promises.unlink(targetAbs);
    return true;
  } catch (err) {
    const e = err as NodeJS.ErrnoException;
    if (e.code === 'ENOENT') return false;
    throw err;
  }
}

// Resolves a public upload URL to its safe absolute disk path.
// Returns null when the URL does not point inside the configured upload root.
export function getStoredFilePath(fileUrl: string): string | null {
  const uploadRoot = getUploadRoot();
  const prefix = `/${uploadRoot}/`;
  if (!fileUrl.startsWith(prefix)) {
    return null;
  }

  const relative = fileUrl.slice(prefix.length);
  if (!relative || relative.includes('..')) {
    return null;
  }

  // Double-check the final path is still inside the upload folder,
  // so a crafted URL can never delete files elsewhere on the server.
  const rootAbs = path.join(process.cwd(), uploadRoot);
  const targetAbs = path.normalize(path.join(rootAbs, relative));
  if (targetAbs !== rootAbs && !targetAbs.startsWith(rootAbs + path.sep)) {
    return null;
  }

  return targetAbs;
}

// Keeps a readable, safe filename while preserving Unicode letters such as Khmer.
function toSafeFilename(originalName: string): string {
  const decodedName = decodeMulterFilename(originalName).normalize('NFC');
  const base = path.basename(decodedName.replace(/\\/g, '/') || 'file');
  const ext = path.extname(base);
  const nameWithoutExt = path.basename(base, ext);

  const safeName =
    nameWithoutExt
      .replace(/[\u0000-\u001F\u007F]/g, '')
      .replace(/[^\p{L}\p{M}\p{N} ._()-]+/gu, '-')
      .replace(/\s+/g, ' ')
      .replace(/-+/g, '-')
      .replace(/^[-. ]+|[-. ]+$/g, '') || 'file';

  const safeExt = ext.replace(/[^a-zA-Z0-9.]+/g, '').slice(0, 10);

  return `${safeName}${safeExt}`;
}

// Multer may expose UTF-8 upload names as Latin-1 text. Decode only names
// containing Latin-1 control/extended bytes, so already-correct Khmer names
// and normal ASCII names stay unchanged.
function decodeMulterFilename(originalName: string): string {
  if (!/[\u0080-\u00FF]/.test(originalName)) {
    return originalName;
  }

  const decodedName = Buffer.from(originalName, 'latin1').toString('utf8');

  return decodedName.includes('\uFFFD') ? originalName : decodedName;
}

// Keeps only safe characters in the folder name.
function toSafeFolderSegment(folderName?: string): string {
  if (!folderName) {
    return '';
  }

  return folderName
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9_-]+/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-+|-+$/g, '');
}
