import path from "node:path";
import fs from "node:fs/promises";
import crypto from "node:crypto";
import sharp, { type Metadata as SharpMetadata } from "sharp";
import { db, MediaItem } from "@/lib/admin/db";
import {
  isCloudinaryConfigured,
  uploadBufferToCloudinary,
  deleteAssetFromCloudinary,
  CloudinaryMediaCategory,
  getDynamicCloudinaryFolder,
} from "./cloudinary";

const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB

export interface ValidatedFile {
  buffer: Buffer;
  originalName: string;
  mimeType: string;
  extension: string;
  size: number;
}

/**
 * Validates file signature (magic bytes) to prevent spoofed uploads or malicious binaries.
 * Supports all common raster formats: JPEG, PNG, WebP, GIF, BMP, TIFF, and AVIF.
 */
export function validateImageSignature(buffer: Buffer): {
  valid: boolean;
  detectedMime?: string;
  extension?: string;
  error?: string;
} {
  if (buffer.length < 12) {
    return { valid: false, error: "File too small or corrupted." };
  }

  // PNG: 89 50 4E 47 0D 0A 1A 0A
  if (
    buffer[0] === 0x89 &&
    buffer[1] === 0x50 &&
    buffer[2] === 0x4e &&
    buffer[3] === 0x47 &&
    buffer[4] === 0x0d &&
    buffer[5] === 0x0a &&
    buffer[6] === 0x1a &&
    buffer[7] === 0x0a
  ) {
    return { valid: true, detectedMime: "image/png", extension: "png" };
  }

  // JPEG: FF D8 FF
  if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
    return { valid: true, detectedMime: "image/jpeg", extension: "jpg" };
  }

  // WebP: 'RIFF' .... 'WEBP'
  if (
    buffer[0] === 0x52 &&
    buffer[1] === 0x49 &&
    buffer[2] === 0x46 &&
    buffer[3] === 0x46 &&
    buffer[8] === 0x57 &&
    buffer[9] === 0x45 &&
    buffer[10] === 0x42 &&
    buffer[11] === 0x50
  ) {
    return { valid: true, detectedMime: "image/webp", extension: "webp" };
  }

  // GIF: GIF87a or GIF89a
  if (
    buffer[0] === 0x47 &&
    buffer[1] === 0x49 &&
    buffer[2] === 0x46 &&
    buffer[3] === 0x38 &&
    (buffer[4] === 0x37 || buffer[4] === 0x39) &&
    buffer[5] === 0x61
  ) {
    return { valid: true, detectedMime: "image/gif", extension: "gif" };
  }

  // BMP: 42 4D ('BM')
  if (buffer[0] === 0x42 && buffer[1] === 0x4d) {
    return { valid: true, detectedMime: "image/bmp", extension: "bmp" };
  }

  // TIFF: 'II' (0x49 0x49 0x2A 0x00) or 'MM' (0x4D 0x4D 0x00 0x2A)
  if (
    (buffer[0] === 0x49 && buffer[1] === 0x49 && buffer[2] === 0x2a && buffer[3] === 0x00) ||
    (buffer[0] === 0x4d && buffer[1] === 0x4d && buffer[2] === 0x00 && buffer[3] === 0x2a)
  ) {
    return { valid: true, detectedMime: "image/tiff", extension: "tiff" };
  }

  // AVIF: offset 4 to 12 contains 'ftypavif' or 'ftypavis'
  if (buffer.length >= 16) {
    const ftypBox = buffer.subarray(4, 12).toString("latin1");
    if (ftypBox.includes("avif") || ftypBox.includes("avis")) {
      return { valid: true, detectedMime: "image/avif", extension: "avif" };
    }
  }

  return {
    valid: false,
    error: "Invalid file signature. Only authentic image files (JPEG, PNG, WebP, GIF, BMP, TIFF, AVIF) are allowed.",
  };
}

/**
 * Slugifies an image filename according to strict admin naming rules:
 * - lowercase
 * - trim whitespace
 * - remove file extension
 * - replace spaces with hyphens
 * - remove unsupported punctuation
 * - normalize repeated hyphens
 * - remove leading/trailing hyphens
 * - preserve meaningful alphanumeric characters
 * - safely normalize Unicode characters (diacritics, em-dash, en-dash)
 * - no random characters, timestamps, UUIDs, or hashes
 *
 * Example:
 * "AI Voice Agents for Healthcare.png" -> "ai-voice-agents-for-healthcare"
 * "About Quto AI Hero Image.webp" -> "about-quto-ai-hero-image"
 * "Healthcare AI Agents — Banner.png" -> "healthcare-ai-agents-banner"
 * "CRM & WhatsApp Automation.jpg" -> "crm-whatsapp-automation"
 * "My Awesome Image (Final).png" -> "my-awesome-image-final"
 * "AI & WhatsApp — Growth!.png" -> "ai-whatsapp-growth"
 * "Transparent Robot.png" -> "transparent-robot"
 * "Quto Logo.webp" -> "quto-logo"
 * "Test Hero Image.jpg" -> "test-hero-image"
 */
export function slugifyImageFileName(fileName: string): string {
  const parsed = path.parse(fileName);
  let baseName = parsed.name || fileName;
  baseName = baseName.replace(/\.(png|jpe?g|webp|gif|bmp|tiff?|avif|svg)$/i, "");

  let slug = baseName
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "") // strip diacritics
    .replace(/[—–_]/g, "-") // em-dash, en-dash, underscore -> hyphen
    .toLowerCase();

  // Replace symbols/punctuation with spaces (keep only alphanumeric and hyphens)
  slug = slug.replace(/[^a-z0-9-]/g, " ");

  // Collapse spaces and multiple hyphens into a single hyphen
  slug = slug.replace(/[\s-]+/g, "-");

  // Trim leading/trailing hyphens
  slug = slug.replace(/^-+|-+$/g, "");

  return slug || "image";
}

// In-memory promise map to deduplicate concurrent in-flight upload requests with the same uploadId
const inFlightUploads = new Map<string, Promise<MediaItem & { isDuplicate?: boolean }>>();

export interface SaveMediaResult extends MediaItem {
  isDuplicate?: boolean;
}

/**
 * Saves an uploaded file to Cloudinary and creates or updates entry in MongoDB.
 * AUTOMATIC WEBP CONVERSION (§Part 9):
 * All valid incoming images are decoded with sharp and converted to high-quality WebP before storage.
 * Strict alpha channel transparency is preserved (never flattened onto a solid background).
 *
 * IDEMPOTENCY & DEDUPLICATION:
 * If an uploadId is provided:
 * 1. Checks if a media asset with this uploadId already exists in MongoDB and returns it immediately.
 * 2. Deduplicates concurrent in-flight requests with the same uploadId using an in-memory lock.
 * 3. Enforces database uniqueness on uploadId to eliminate race conditions.
 *
 * SLUG & NAMING RULES:
 * The original filename is slugified without extensions or random suffixes.
 * If replaceMediaId is provided, reuses the existing public ID and overwrites the asset.
 * If a new upload collides with an existing asset, deterministic suffixes (-2, -3, ...) are used.
 */
export async function saveUploadedMedia(
  file: File,
  uploaderEmail: string,
  altText?: string,
  category: CloudinaryMediaCategory = "general",
  replaceMediaId?: string,
  uploadId?: string
): Promise<SaveMediaResult> {
  // 0. Idempotency Check: if this uploadId has already completed, return existing record immediately
  if (uploadId) {
    const existing = await db.getMediaByUploadId(uploadId);
    if (existing) {
      console.log(`[saveUploadedMedia] Idempotency match: uploadId "${uploadId}" already exists as "${existing.id}"`);
      return { ...existing, isDuplicate: true };
    }

    // Check if an upload with this uploadId is already actively processing in-flight
    const activePromise = inFlightUploads.get(uploadId);
    if (activePromise) {
      console.log(`[saveUploadedMedia] In-flight deduplication: awaiting active upload for uploadId "${uploadId}"`);
      return await activePromise;
    }
  }

  const doUpload = async (): Promise<SaveMediaResult> => {
    const arrayBuffer = await file.arrayBuffer();
    const rawBuffer = Buffer.from(arrayBuffer);

    if (rawBuffer.length > MAX_FILE_SIZE_BYTES) {
      throw new Error(`File size exceeds limit (${(rawBuffer.length / (1024 * 1024)).toFixed(1)}MB). Max allowed is 10MB.`);
    }

  // 1. Validate magic bytes signature
  const signatureCheck = validateImageSignature(rawBuffer);
  if (!signatureCheck.valid) {
    throw new Error(signatureCheck.error || "File signature verification failed.");
  }

  // 2. Decode and validate structure using sharp
  let inputMeta: SharpMetadata;
  try {
    inputMeta = await sharp(rawBuffer).metadata();
  } catch (decodeErr: any) {
    throw new Error(`Failed to decode image data: ${decodeErr.message || "Corrupted image file."}`);
  }

  if (!inputMeta.width || !inputMeta.height) {
    throw new Error("Invalid image: unable to read image dimensions.");
  }

  // 3. Convert to WebP preserving transparency and dimensions (§Part 9)
  let webpBuffer: Buffer;
  try {
    webpBuffer = await sharp(rawBuffer)
      .webp({
        quality: 88,
        alphaQuality: 100, // strictly preserve transparency without degradation
        lossless: false,
        effort: 4,
      })
      .toBuffer();
  } catch (convErr: any) {
    throw new Error(`Image WebP conversion failed: ${convErr.message || "Conversion error."}`);
  }

  const webpMeta = await sharp(webpBuffer).metadata();

  // Cloudinary is REQUIRED for all dynamic/admin-managed media. There is NO
  // silent local-disk fallback: a local write would break on Vercel's ephemeral
  // serverless filesystem and split media across two storages. Fail loudly.
  if (!isCloudinaryConfigured()) {
    throw new Error(
      "Image storage is not configured correctly. Cloudinary credentials are missing — uploads are disabled until they are set."
    );
  }

  const now = new Date();
  let targetPublicId = "";
  let filename = "";
  let customFolder: string | undefined;
  let existingMediaToReplace: MediaItem | null = null;

  if (replaceMediaId) {
    existingMediaToReplace = await db.getMediaById(replaceMediaId);
  }

  if (existingMediaToReplace) {
    // Intentional replacement for the same record:
    // Reuse existing publicId, overwrite in place, preserve original filename
    const fullExistingId = existingMediaToReplace.publicId || path.parse(existingMediaToReplace.filename).name;
    const lastSlash = fullExistingId.lastIndexOf("/");
    targetPublicId = lastSlash !== -1 ? fullExistingId.slice(lastSlash + 1) : fullExistingId;
    customFolder = existingMediaToReplace.folder || (lastSlash !== -1 ? fullExistingId.slice(0, lastSlash) : undefined);
    filename = existingMediaToReplace.filename;
  } else {
    // New upload: slugify original filename and deterministically resolve collisions
    const baseSlug = slugifyImageFileName(file.name);
    const targetFolder = getDynamicCloudinaryFolder(category);
    let counter = 1;

    while (true) {
      const candidateSlug = counter === 1 ? baseSlug : `${baseSlug}-${counter}`;
      const candidateFilename = `${candidateSlug}.webp`;
      const candidateFullPublicId = `${targetFolder}/${candidateSlug}`;

      const [byPublicIdFull, byPublicIdBare, byFilename] = await Promise.all([
        db.getMediaByPublicId(candidateFullPublicId),
        db.getMediaByPublicId(candidateSlug),
        db.getMediaByFilename(candidateFilename),
      ]);

      if (!byPublicIdFull && !byPublicIdBare && !byFilename) {
        targetPublicId = candidateSlug;
        filename = candidateFilename;
        break;
      }
      counter++;
    }
  }

  try {
    const cloudResult = await uploadBufferToCloudinary(webpBuffer, {
      category,
      customFolder,
      publicId: targetPublicId,
      overwrite: true,
    });

    if (existingMediaToReplace) {
      const updatedItem: MediaItem = {
        ...existingMediaToReplace,
        url: cloudResult.secureUrl,
        mimeType: "image/webp",
        size: webpBuffer.length,
        width: webpMeta.width || inputMeta.width || 0,
        height: webpMeta.height || inputMeta.height || 0,
        publicId: cloudResult.publicId,
        secureUrl: cloudResult.secureUrl,
        resourceType: cloudResult.resourceType,
        format: "webp",
        bytes: webpBuffer.length,
        folder: cloudResult.folder,
        uploadedAt: now.toISOString(),
        uploadedBy: uploaderEmail,
      };
      await db.updateMedia(existingMediaToReplace.id, updatedItem);
      return updatedItem;
    }

    const mediaItem: MediaItem = {
      id: crypto.randomUUID(),
      uploadId: uploadId || null,
      filename,
      originalName: file.name,
      url: cloudResult.secureUrl,
      mimeType: "image/webp",
      size: webpBuffer.length,
      width: webpMeta.width || inputMeta.width || 0,
      height: webpMeta.height || inputMeta.height || 0,
      alt: altText || targetPublicId.replace(/[-_]+/g, " "),
      caption: null,
      usedInPosts: [],
      uploadedBy: uploaderEmail,
      uploadedAt: now.toISOString(),
      provider: "cloudinary",
      publicId: cloudResult.publicId,
      secureUrl: cloudResult.secureUrl,
      resourceType: cloudResult.resourceType,
      format: "webp",
      bytes: webpBuffer.length,
      folder: cloudResult.folder,
    };

    const created = await db.createMedia(mediaItem);
    return { ...created, isDuplicate: created.id !== mediaItem.id };
  } catch (err: any) {
    console.error("[saveUploadedMedia] Cloudinary upload failed:", err);
    throw new Error(`Cloudinary upload failed: ${err.message || "Unknown error"}`);
  }
};

  if (uploadId) {
    const uploadPromise = doUpload().finally(() => {
      inFlightUploads.delete(uploadId);
    });
    inFlightUploads.set(uploadId, uploadPromise);
    return await uploadPromise;
  }

  return await doUpload();
}

export interface AssetUsageResult {
  isUsed: boolean;
  usageCount: number;
  usageLocations: string[];
  referencingEntities: {
    articles: { id: string; title: string; slug: string }[];
    authors: { id: string; name: string }[];
    categories: { id: string; name: string }[];
  };
}

/**
 * Checks all content entities across the application to see if a media asset is referenced.
 * An asset is considered orphan ONLY if usageCount === 0.
 * Searches across posts (featured image, OG image, twitter image, inline images, sections),
 * author profiles (avatar URL, avatarMediaId), and categories.
 */
export async function getAssetUsage(
  identifierOrItem: string | MediaItem
): Promise<AssetUsageResult> {
  let item: MediaItem | null = null;
  const searchTokens = new Set<string>();

  if (typeof identifierOrItem === "string") {
    // Search DB by ID, URL, publicId, or filename
    item =
      (await db.getMediaById(identifierOrItem)) ||
      (await db.getMediaByUrl(identifierOrItem)) ||
      (await db.getMediaByPublicId(identifierOrItem)) ||
      (await db.getMediaByFilename(identifierOrItem));

    searchTokens.add(identifierOrItem);
    if (identifierOrItem.includes("/")) {
      const last = identifierOrItem.split("/").pop();
      if (last) searchTokens.add(last);
    }
  } else {
    item = identifierOrItem;
  }

  if (item) {
    if (item.id) searchTokens.add(item.id);
    if (item.url) searchTokens.add(item.url);
    if (item.publicId) {
      searchTokens.add(item.publicId);
      const stripped = item.publicId.split("/").pop();
      if (stripped) searchTokens.add(stripped);
    }
    if (item.filename) {
      searchTokens.add(item.filename);
      const nameOnly = path.parse(item.filename).name;
      if (nameOnly) searchTokens.add(nameOnly);
    }
  }

  const tokens = Array.from(searchTokens).filter(Boolean);

  const [allPosts, allUsers, allCategories] = await Promise.all([
    db.getPosts({ limit: 1000 }),
    db.getUsers(),
    db.getCategories(),
  ]);

  const matchedArticles: { id: string; title: string; slug: string }[] = [];
  for (const post of allPosts) {
    const postJson = JSON.stringify(post);
    const matches = tokens.some((tok) => {
      if (post.featuredImageId && post.featuredImageId === tok) return true;
      if (post.featuredImage && post.featuredImage.includes(tok)) return true;
      if (post.ogImageId && post.ogImageId === tok) return true;
      if (post.ogImage && post.ogImage.includes(tok)) return true;
      if (post.twitterImageId && post.twitterImageId === tok) return true;
      if (post.twitterImage && post.twitterImage.includes(tok)) return true;
      return postJson.includes(tok);
    });

    if (matches) {
      matchedArticles.push({ id: post.id, title: post.title, slug: post.slug });
    }
  }

  const matchedAuthors: { id: string; name: string }[] = [];
  for (const user of allUsers) {
    const matches = tokens.some((tok) => {
      if (user.avatarMediaId && user.avatarMediaId === tok) return true;
      if (user.avatarUrl && user.avatarUrl.includes(tok)) return true;
      return false;
    });

    if (matches) {
      matchedAuthors.push({ id: user.id, name: user.name });
    }
  }

  const matchedCategories: { id: string; name: string }[] = [];
  for (const cat of allCategories) {
    const catJson = JSON.stringify(cat);
    const matches = tokens.some((tok) => catJson.includes(tok));
    if (matches) {
      matchedCategories.push({ id: cat.id, name: cat.name });
    }
  }

  const usageLocations: string[] = [];
  for (const a of matchedArticles) {
    usageLocations.push(`Article: "${a.title}"`);
  }
  for (const u of matchedAuthors) {
    usageLocations.push(`Author Profile: "${u.name}"`);
  }
  for (const c of matchedCategories) {
    usageLocations.push(`Category: "${c.name}"`);
  }

  const usageCount = matchedArticles.length + matchedAuthors.length + matchedCategories.length;

  return {
    isUsed: usageCount > 0,
    usageCount,
    usageLocations,
    referencingEntities: {
      articles: matchedArticles,
      authors: matchedAuthors,
      categories: matchedCategories,
    },
  };
}

/**
 * Checks which posts and author profiles currently reference a media item (by URL or ID).
 */
export async function getMediaReferences(mediaIdentifier: string): Promise<{
  posts: string[];
  authors: string[];
  total: number;
}> {
  const usage = await getAssetUsage(mediaIdentifier);
  return {
    posts: usage.referencingEntities.articles.map((a) => a.title),
    authors: usage.referencingEntities.authors.map((u) => u.name),
    total: usage.usageCount,
  };
}

/**
 * Backwards-compatible post usage check.
 */
export async function getMediaUsageInPosts(mediaIdentifier: string): Promise<string[]> {
  const refs = await getMediaReferences(mediaIdentifier);
  return refs.posts;
}

/**
 * Unlinks the physical file from public storage if it exists on local disk.
 */
export async function deletePhysicalMediaFile(mediaUrl: string): Promise<boolean> {
  try {
    const cleanPath = mediaUrl.replace(/^\//, "");
    const localFilePath = path.join(process.cwd(), "public", cleanPath);
    await fs.unlink(localFilePath);
    return true;
  } catch {
    return false;
  }
}

/**
 * Safely cleans up a media asset when replaced or removed.
 * Strictly verifies whether ANY remaining references exist across MongoDB posts or author profiles.
 * If unreferenced anywhere, deletes the asset from Cloudinary (or disk) and database.
 */
export async function safeCleanupUnreferencedMedia(
  mediaIdOrUrl: string
): Promise<{ cleaned: boolean; inUse?: boolean; references?: string[] }> {
  let media: MediaItem | null = null;
  if (mediaIdOrUrl.startsWith("/") || mediaIdOrUrl.startsWith("http")) {
    media = await db.getMediaByUrl(mediaIdOrUrl);
  } else {
    media = await db.getMediaById(mediaIdOrUrl);
    if (!media) {
      media = await db.getMediaByUrl(mediaIdOrUrl);
    }
  }

  if (!media) {
    return { cleaned: false };
  }

  // Check references by both URL and ID across all posts and authors
  const [refsByUrl, refsById] = await Promise.all([
    getMediaReferences(media.url),
    getMediaReferences(media.id),
  ]);

  const allPostRefs = Array.from(new Set([...refsByUrl.posts, ...refsById.posts]));
  const allAuthorRefs = Array.from(new Set([...refsByUrl.authors, ...refsById.authors]));
  const allRefs = [
    ...allPostRefs.map((p) => `Post: "${p}"`),
    ...allAuthorRefs.map((a) => `Author: "${a}"`),
  ];

  if (allRefs.length > 0) {
    return { cleaned: false, inUse: true, references: allRefs };
  }

  // Unreferenced: Safe to delete from Cloudinary or local disk
  if (media.provider === "cloudinary" && media.publicId) {
    await deleteAssetFromCloudinary(media.publicId);
  } else if (media.url.startsWith("/")) {
    await deletePhysicalMediaFile(media.url);
  }

  await db.deleteMedia(media.id);
  return { cleaned: true };
}
