import { rgbaToThumbHash } from "thumbhash";
import ExifReader from "exifreader";
import type { ImageMetadata } from "astro";
import { readFile } from "node:fs/promises";
import sharp from "sharp";
import { createHash } from "node:crypto";
import { promises as fs } from "node:fs";
import path from "node:path";

export async function generateThumbHash(
  buffer: ArrayBuffer,
): Promise<string | undefined> {
  if (!buffer) return;

  const sp = sharp(buffer);

  const meta = await sp.metadata();
  const scaleFactor = 100 / Math.max(meta.width, meta.height);
  const smallWidth = Math.floor(meta.width * scaleFactor);
  const smallHeight = Math.floor(meta.height * scaleFactor);

  const smallImg = await sp
    .resize(smallWidth, smallHeight)
    .withMetadata()
    .raw()
    .ensureAlpha()
    .toBuffer();

  const hashBuffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
  return Buffer.from(hashBuffer).toString("base64");
}

const allowedExif = [
  "ApertureValue",
  "DateTimeOriginal",
  "ShutterSpeedValue",
  "ExposureTime",
  "ApertureValue",
  "FNumber",
  "FocalLength",
  "GPSLatitude",
  "GPSLongitude",
  "GPSAltitude",
  "IsoSpeedRatings",
  "Make",
  "Model",
];

export async function getImageBuffer(
  image: ImageMetadata,
): Promise<ArrayBuffer> {
  if (image.format === "svg") return undefined; // SVGs don't have EXIF data
  const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath ??
    image.src;

  if (!imagePath) return undefined;

  try {
    if (imagePath.startsWith("https://") || imagePath.startsWith("http://")) {
      const res = await fetch(imagePath, { signal: AbortSignal.timeout(5000) });
      return await res.arrayBuffer();
    } else {
      const b = await readFile(imagePath);
      return b.buffer as ArrayBuffer;
    }
  } catch (err) {
    return undefined;
  }
}

export async function getExifData(buffer: ArrayBuffer) {
  if (!buffer) return undefined;

  const tags = await ExifReader.load(buffer, { async: true });

  const out: Record<string, unknown> = {};
  let hasExif = false;

  for (const key of allowedExif) {
    if (!tags[key]) continue;
    hasExif = true;
    out[key] = tags[key]?.description;
  }

  return hasExif ? out : undefined;
}

const CACHE_DIR = path.join(process.cwd(), 'node_modules', '.astro', 'image-cache');

export async function getProcessedImage(image: ImageMetadata) {
    const buffer = await getImageBuffer(image);
    if (!buffer) {
        return { thumbhash: undefined, exif: undefined };
    }

    const hash = createHash('sha256').update(new Uint8Array(buffer)).digest('hex');
    const cacheFile = path.join(CACHE_DIR, `${hash}.json`);

    try {
        const cachedData = await fs.readFile(cacheFile, 'utf-8');
        return JSON.parse(cachedData);
    } catch (e) {
        if (e.code !== 'ENOENT') {
            console.error("Failed to read from image cache:", e);
        }
    }

    const thumbhash = await generateThumbHash(buffer);
    const exif = await getExifData(buffer);

    try {
        await fs.mkdir(CACHE_DIR, { recursive: true });
        await fs.writeFile(cacheFile, JSON.stringify({ thumbhash, exif }), 'utf-8');
    } catch (writeError) {
        console.error("Failed to write to image cache:", writeError);
    }

    return { thumbhash, exif };
}
