import "server-only";
import { promises as fs } from "fs";
import path from "path";
import { randomBytes } from "crypto";

export interface StorageAdapter {
  put(key: string, data: Buffer, contentType: string): Promise<{ url: string; key: string }>;
  remove(key: string): Promise<void>;
}

const ROOT = path.resolve(process.cwd(), process.env.LOCAL_STORAGE_DIR ?? "./storage/uploads");

/** Files persist on disk (outside .next) and are served by src/app/media/[...path]/route.ts. */
const local: StorageAdapter = {
  async put(key, data) {
    const full = path.join(ROOT, key);
    if (!full.startsWith(ROOT)) throw new Error("Invalid storage key");
    await fs.mkdir(path.dirname(full), { recursive: true });
    await fs.writeFile(full, data);
    return { key, url: `/media/${key}` };
  },
  async remove(key) {
    const full = path.join(ROOT, key);
    if (!full.startsWith(ROOT)) return;
    await fs.rm(full, { force: true });
  },
};

/** S3-compatible storage (AWS S3, Cloudflare R2, Spaces). Install with: npm i @aws-sdk/client-s3 */
function s3(): StorageAdapter {
  const load = async () => {
    const modName = "@aws-sdk/client-s3";
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const sdk: any = await import(/* webpackIgnore: true */ modName);
    const client = new sdk.S3Client({
      region: process.env.S3_REGION || "auto",
      endpoint: process.env.S3_ENDPOINT || undefined,
      credentials: { accessKeyId: process.env.S3_ACCESS_KEY_ID!, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY! },
    });
    return { sdk, client };
  };
  return {
    async put(key, data, contentType) {
      const { sdk, client } = await load();
      await client.send(new sdk.PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: key, Body: data, ContentType: contentType, CacheControl: "public, max-age=31536000, immutable" }));
      return { key, url: `${process.env.S3_PUBLIC_URL?.replace(/\/$/, "")}/${key}` };
    },
    async remove(key) {
      const { sdk, client } = await load();
      await client.send(new sdk.DeleteObjectCommand({ Bucket: process.env.S3_BUCKET, Key: key }));
    },
  };
}

export function storage(): StorageAdapter {
  return process.env.STORAGE_DRIVER === "s3" ? s3() : local;
}

export const ALLOWED_MEDIA: Record<string, string> = {
  "image/jpeg": "jpg",
  "image/png": "png",
  "image/webp": "webp",
  "image/avif": "avif",
  "video/mp4": "mp4",
  "video/webm": "webm",
};
export const MAX_IMAGE_BYTES = 8 * 1024 * 1024;
export const MAX_VIDEO_BYTES = 40 * 1024 * 1024;

/** Checks magic bytes so a renamed file can't pass as an image. */
export function sniffType(buf: Buffer): string | null {
  if (buf.length < 12) return null;
  if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return "image/jpeg";
  if (buf.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) return "image/png";
  if (buf.subarray(0, 4).toString() === "RIFF" && buf.subarray(8, 12).toString() === "WEBP") return "image/webp";
  if (buf.subarray(4, 8).toString() === "ftyp") {
    const brand = buf.subarray(8, 12).toString();
    if (brand.startsWith("avi")) return "image/avif";
    return "video/mp4";
  }
  if (buf[0] === 0x1a && buf[1] === 0x45 && buf[2] === 0xdf && buf[3] === 0xa3) return "video/webm";
  return null;
}

export function newKey(folder: string, ext: string) {
  const d = new Date();
  const safeFolder = folder.replace(/[^a-z0-9-]/gi, "").toLowerCase() || "general";
  return `${safeFolder}/${d.getUTCFullYear()}/${String(d.getUTCMonth() + 1).padStart(2, "0")}/${randomBytes(10).toString("hex")}.${ext}`;
}

/** Image dimensions for JPEG/PNG/WebP without native deps. */
export function imageSize(buf: Buffer): { width: number; height: number } | null {
  try {
    if (buf[0] === 0x89) return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
    if (buf[0] === 0xff) {
      let i = 2;
      while (i < buf.length) {
        if (buf[i] !== 0xff) return null;
        const marker = buf[i + 1];
        const len = buf.readUInt16BE(i + 2);
        if (marker >= 0xc0 && marker <= 0xcf && ![0xc4, 0xc8, 0xcc].includes(marker)) return { height: buf.readUInt16BE(i + 5), width: buf.readUInt16BE(i + 7) };
        i += 2 + len;
      }
    }
    if (buf.subarray(0, 4).toString() === "RIFF") {
      const chunk = buf.subarray(12, 16).toString();
      if (chunk === "VP8X") return { width: 1 + buf.readUIntLE(24, 3), height: 1 + buf.readUIntLE(27, 3) };
      if (chunk === "VP8 ") return { width: buf.readUInt16LE(26) & 0x3fff, height: buf.readUInt16LE(28) & 0x3fff };
      if (chunk === "VP8L") { const b = buf.readUInt32LE(21); return { width: (b & 0x3fff) + 1, height: ((b >> 14) & 0x3fff) + 1 }; }
    }
  } catch { /* fallthrough */ }
  return null;
}
