/**
 * 议程附件 v1.0 常量
 *
 * 涵盖：
 * - MIME 白名单（8 项，写入文档 06-data-model §文件存储约定 对齐）
 * - 单文件大小上限（200MB）
 * - 扩展名 → MIME 反查（用于 magic bytes 二次校验时区分白名单内 alias）
 * - 存储 root / cron 周期 / 物理删天数等 env helper
 */

/** MIME 白名单（v1.0 锁定）—— 8 项，不在白名单一律 415 ATTACHMENT_MIME_NOT_ALLOWED。 */
export const ALLOWED_MIME_TYPES = [
  'application/pdf',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'image/jpeg',
  'image/png',
  'video/mp4',
  'video/quicktime',
] as const;

export type AllowedMimeType = (typeof ALLOWED_MIME_TYPES)[number];

/** 单文件大小上限（200MB），超出抛 413 ATTACHMENT_TOO_LARGE。 */
export const MAX_FILE_SIZE_BYTES = 200 * 1024 * 1024;

/** MIME → 落盘扩展名（用于 storagePath `<yyyy>/<mm>/<uuid>.<ext>`）。 */
export const EXT_BY_MIME: Record<string, string> = {
  'application/pdf': 'pdf',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
  'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
  'image/jpeg': 'jpg',
  'image/png': 'png',
  'video/mp4': 'mp4',
  'video/quicktime': 'mov',
};

/**
 * Magic bytes 二次校验的「等价 MIME」分组。
 *
 * file-type 探测出来的 MIME 跟 multer 报的 Content-Type **不要求完全字符串相等**——
 * 比如 file-type 对 docx/xlsx/pptx 一律返回上层 ZIP MIME（`application/zip` 或
 * Office OOXML 父 MIME），跟客户端发的 OOXML 子 MIME 不等。我们用「等价分组」放过
 * 这种已知的合法 alias，同时保留对 `.exe` 改扩展名伪装 `.pdf` 这种核心威胁的拦截。
 *
 * 实际匹配规则：file-type 探测出的 MIME ∈ 该分组 → 视为一致。
 */
export const MIME_EQUIVALENCE_GROUPS: Record<string, string[]> = {
  'application/pdf': ['application/pdf'],
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': [
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/zip',
    'application/x-zip-compressed',
  ],
  'application/vnd.openxmlformats-officedocument.presentationml.presentation': [
    'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'application/zip',
    'application/x-zip-compressed',
  ],
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': [
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'application/zip',
    'application/x-zip-compressed',
  ],
  'image/jpeg': ['image/jpeg', 'image/jpg'],
  'image/png': ['image/png'],
  'video/mp4': ['video/mp4', 'video/x-m4v'],
  'video/quicktime': ['video/quicktime', 'video/mov'],
};

/**
 * 判定 file-type 探测的 MIME 是否与客户端声明的 Content-Type「等价」。
 * 返回 true → 通过；false → 抛 415 ATTACHMENT_MIME_MISMATCH。
 */
export function isMimeEquivalent(declared: string, detected: string | undefined): boolean {
  if (!detected) return false;
  const group = MIME_EQUIVALENCE_GROUPS[declared];
  if (!group) return false;
  return group.includes(detected);
}

/** 议程附件 v1.0 env helper（带默认值）。 */
export const getStorageRoot = (): string =>
  process.env.MEETING_ATTACHMENT_STORAGE_ROOT?.trim() || './var/meeting-attachments';

export const getTmpUploadDir = (): string => `${getStorageRoot()}/tmp/uploads`;

export const getGcIntervalHours = (): number => {
  const raw = process.env.MEETING_ATTACHMENT_GC_INTERVAL_HOURS;
  const parsed = raw ? Number.parseInt(raw, 10) : NaN;
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
};

export const getPhysicalDeleteDays = (): number => {
  const raw = process.env.MEETING_ATTACHMENT_PHYSICAL_DELETE_DAYS;
  const parsed = raw ? Number.parseInt(raw, 10) : NaN;
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 30;
};
