import { v2 as cloudinary, type UploadApiResponse } from "cloudinary";

/**
 * Server-only Cloudinary configuration.
 *
 * Config is read LAZILY (at call time), not captured in a module-load `const`.
 * A top-level const reads process.env exactly once when the module is first
 * imported — so if that first import happened before the credentials were
 * present (e.g. the dev server was started before .env got the keys; a plain
 * env reload does not re-run a top-level const), the flag would be stuck
 * `false` forever and every upload would silently fall back to local disk.
 * Reading env on each call removes that entire class of bug.
 *
 * API secrets NEVER leak to the client — this module is imported only by
 * server code (route handlers / server utilities).
 */

function readEnv() {
  return {
    cloudName: process.env.CLOUDINARY_CLOUD_NAME,
    apiKey: process.env.CLOUDINARY_API_KEY,
    apiSecret: process.env.CLOUDINARY_API_SECRET,
  };
}

/** True only when all three Cloudinary credentials are present at call time. */
export function isCloudinaryConfigured(): boolean {
  const { cloudName, apiKey, apiSecret } = readEnv();
  return Boolean(cloudName && apiKey && apiSecret);
}

export class CloudinaryNotConfiguredError extends Error {
  constructor() {
    super(
      "Image storage is not configured correctly. Set CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY and CLOUDINARY_API_SECRET."
    );
    this.name = "CloudinaryNotConfiguredError";
  }
}

let configuredForCloud: string | null = null;

/** Configures the SDK on first use (idempotent) and returns the client. Throws if unconfigured. */
function getClient() {
  const { cloudName, apiKey, apiSecret } = readEnv();
  if (!cloudName || !apiKey || !apiSecret) {
    throw new CloudinaryNotConfiguredError();
  }
  // Re-apply config if the cloud name changed (or first run).
  if (configuredForCloud !== cloudName) {
    cloudinary.config({ cloud_name: cloudName, api_key: apiKey, api_secret: apiSecret, secure: true });
    configuredForCloud = cloudName;
  }
  return cloudinary;
}

/**
 * Safe diagnostic for the health-check endpoint. Returns only non-secret state.
 * The cloud name is NOT secret (it appears in every public delivery URL); the
 * API key and secret are never returned.
 */
export function getCloudinaryStatus(): {
  configured: boolean;
  hasCloudName: boolean;
  hasApiKey: boolean;
  hasApiSecret: boolean;
  cloudName: string | null;
} {
  const { cloudName, apiKey, apiSecret } = readEnv();
  return {
    configured: Boolean(cloudName && apiKey && apiSecret),
    hasCloudName: Boolean(cloudName),
    hasApiKey: Boolean(apiKey),
    hasApiSecret: Boolean(apiSecret),
    cloudName: cloudName || null,
  };
}

/** Verifies the credentials are actually accepted by Cloudinary (a real API round-trip). */
export async function pingCloudinary(): Promise<{ ok: boolean; error?: string }> {
  try {
    const client = getClient();
    await client.api.ping();
    return { ok: true };
  } catch (err) {
    return { ok: false, error: (err as Error).message };
  }
}

export type CloudinaryMediaCategory = "featured" | "inline" | "social" | "author" | "general";

/**
 * Standard Cloudinary folder hierarchy (year/month are dynamic):
 *  quto-ai/blog/{year}/{month}/{featured|inline|social}
 *  quto-ai/authors/profiles
 *  quto-ai/general
 */
export function getDynamicCloudinaryFolder(category: CloudinaryMediaCategory = "general"): string {
  const now = new Date();
  const year = String(now.getFullYear());
  const month = String(now.getMonth() + 1).padStart(2, "0");

  switch (category) {
    case "featured":
      return `quto-ai/blog/${year}/${month}/featured`;
    case "inline":
      return `quto-ai/blog/${year}/${month}/inline`;
    case "social":
      return `quto-ai/blog/${year}/${month}/social`;
    case "author":
      return `quto-ai/authors/profiles`;
    case "general":
    default:
      return `quto-ai/general`;
  }
}

export interface CloudinaryUploadResult {
  publicId: string;
  secureUrl: string;
  format: string;
  width: number;
  height: number;
  bytes: number;
  resourceType: string;
  folder: string;
}

/** Uploads an in-memory buffer to Cloudinary via stream upload. Throws if unconfigured. */
export async function uploadBufferToCloudinary(
  buffer: Buffer,
  options: {
    category?: CloudinaryMediaCategory;
    customFolder?: string;
    filename?: string;
    publicId?: string;
    overwrite?: boolean;
  } = {}
): Promise<CloudinaryUploadResult> {
  const client = getClient();
  const folder = options.customFolder || getDynamicCloudinaryFolder(options.category);

  return new Promise((resolve, reject) => {
    const uploadStream = client.uploader.upload_stream(
      {
        folder,
        public_id: options.publicId,
        resource_type: "image",
        use_filename: false,
        unique_filename: false,
        overwrite: options.overwrite ?? true,
        format: "webp",
      },
      (error, result?: UploadApiResponse) => {
        if (error || !result) {
          return reject(error || new Error("Upload to Cloudinary failed with no result."));
        }
        resolve({
          publicId: result.public_id,
          secureUrl: result.secure_url,
          format: result.format,
          width: result.width,
          height: result.height,
          bytes: result.bytes,
          resourceType: result.resource_type,
          folder: result.folder || folder,
        });
      }
    );
    uploadStream.end(buffer);
  });
}

/** Deletes an asset from Cloudinary by public ID. */
export async function deleteAssetFromCloudinary(publicId: string): Promise<boolean> {
  if (!publicId || !isCloudinaryConfigured()) return false;
  try {
    const client = getClient();
    const result = await client.uploader.destroy(publicId, { invalidate: true });
    return result.result === "ok";
  } catch (error) {
    console.error("[deleteAssetFromCloudinary] Error:", error);
    return false;
  }
}
