import { ImageResponse } from "next/og";
import { readFile, writeFile, mkdir } from "node:fs/promises";
import path from "node:path";
import QRCode from "qrcode";
import { buildQrPayload } from "./ticket-signing";
import { uploadsRoot, readUploadIfLocal } from "./storage/local";

const EXT_MIME: Record<string, string> = {
  png: "image/png",
  webp: "image/webp",
  gif: "image/gif",
  jpg: "image/jpeg",
  jpeg: "image/jpeg",
};

// event.cover_image_url is stored as a same-origin relative path for
// locally-uploaded images (e.g. "/api/uploads/events/…") -- satori has no
// browser/page context to resolve a relative URL against, so passing it
// straight to <img src> as-is silently threw during render (confirmed: the
// one ticket whose card image never cached and kept showing broken in
// Gmail was for an event with exactly this kind of cover image). Reading
// the file straight off disk and embedding it as a data: URI sidesteps
// both the relative-URL problem and an unnecessary network round-trip for
// an image already on the same filesystem; only a genuinely external
// (http/https) cover image needs an actual fetch, which satori does handle.
async function resolveCoverImage(url: string | null): Promise<string | null> {
  if (!url) return null;
  const localBuf = await readUploadIfLocal(url);
  if (localBuf) {
    const ext = path.extname(url).slice(1).toLowerCase();
    const mime = EXT_MIME[ext] ?? "image/jpeg";
    return `data:${mime};base64,${localBuf.toString("base64")}`;
  }
  if (/^https?:\/\//i.test(url)) return url;
  return `${process.env.NEXT_PUBLIC_SITE_URL}${url}`;
}

export interface TicketCardRenderData {
  code: string;
  signature: string;
  attendeeName: string | null;
  buyerPhone: string | null;
  ticketTypeName: string | null;
  eventTitle: string;
  venue: string | null;
  city: string | null;
  startsAt: Date;
  endsAt: Date | null;
  organizerName: string | null;
  coverImageUrl: string | null;
  index: number;
}

function cachePath(code: string): string {
  return path.join(uploadsRoot(), "ticket-cards", `${code}.png`);
}

async function renderTicketCardPng(data: TicketCardRenderData): Promise<Buffer> {
  const [interRegular, interBold, qrBuffer, coverImageSrc] = await Promise.all([
    readFile(path.join(process.cwd(), "public/fonts/Inter-Regular.ttf")),
    readFile(path.join(process.cwd(), "public/fonts/Inter-Bold.ttf")),
    QRCode.toBuffer(buildQrPayload(data.code, data.signature), { width: 360, margin: 1 }),
    resolveCoverImage(data.coverImageUrl),
  ]);
  const qrDataUrl = `data:image/png;base64,${qrBuffer.toString("base64")}`;

  const dateFmt = new Intl.DateTimeFormat("en-US", { weekday: "long", month: "long", day: "2-digit", year: "numeric" });
  const timeFmt = new Intl.DateTimeFormat("en-US", { hour: "2-digit", minute: "2-digit" });
  const location = data.venue ? [data.venue, data.city].filter(Boolean).join(", ") : data.city;

  const label = (text: string, right = false) => (
    <div
      style={{
        display: "flex",
        width: "100%",
        justifyContent: right ? "flex-end" : "flex-start",
        fontSize: 16,
        textTransform: "uppercase",
        letterSpacing: 1,
        color: "#888",
      }}
    >
      {text}
    </div>
  );
  const value = (text: string, right = false) => (
    <div
      style={{
        display: "flex",
        width: "100%",
        justifyContent: right ? "flex-end" : "flex-start",
        textAlign: right ? "right" : "left",
        fontSize: 24,
        fontWeight: 700,
        color: "#111",
        marginTop: 4,
      }}
    >
      {text}
    </div>
  );

  const image = new ImageResponse(
    (
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          width: 800,
          height: 1120,
          background: "#fafafa",
          border: "2px solid #e5e5e5",
          borderRadius: 32,
          fontFamily: "Inter",
        }}
      >
        <div style={{ display: "flex", justifyContent: "space-between", padding: "40px 40px 0" }}>
          <div style={{ display: "flex", flexDirection: "column", width: 420 }}>
            {label("Attendee")}
            {value(data.attendeeName || "Guest")}
            {data.buyerPhone && (
              <div style={{ display: "flex", flexDirection: "column", marginTop: 24 }}>
                {label("Phone Number")}
                {value(data.buyerPhone)}
              </div>
            )}
            <div style={{ display: "flex", flexDirection: "column", marginTop: 24 }}>
              {label("Start Date")}
              {value(`${dateFmt.format(data.startsAt)} · ${timeFmt.format(data.startsAt)}`)}
            </div>
          </div>
          <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", width: 260 }}>
            {coverImageSrc && (
              // eslint-disable-next-line @next/next/no-img-element
              <img src={coverImageSrc} width={140} height={140} style={{ borderRadius: 16, objectFit: "cover" }} alt="" />
            )}
            {data.endsAt && (
              <div style={{ display: "flex", flexDirection: "column", marginTop: 24, width: "100%" }}>
                {label("End Date", true)}
                {value(`${dateFmt.format(data.endsAt)} · ${timeFmt.format(data.endsAt)}`, true)}
              </div>
            )}
          </div>
        </div>

        <div style={{ display: "flex", justifyContent: "space-between", padding: "32px 40px 0" }}>
          <div style={{ display: "flex", flexDirection: "column" }}>
            {data.organizerName && (
              <div style={{ display: "flex", flexDirection: "column" }}>
                {label("Storefront")}
                {value(data.organizerName)}
              </div>
            )}
          </div>
          <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", width: 260 }}>
            {data.ticketTypeName && (
              <div style={{ display: "flex", flexDirection: "column", width: "100%" }}>
                {label("Ticket", true)}
                {value(data.ticketTypeName, true)}
              </div>
            )}
          </div>
        </div>

        {location && (
          <div style={{ display: "flex", flexDirection: "column", padding: "32px 40px 0" }}>
            {label("Location")}
            {value(location)}
          </div>
        )}

        <div style={{ display: "flex", marginTop: 40, borderTop: "3px dashed #ccc" }} />

        <div style={{ display: "flex", flexDirection: "column", alignItems: "center", padding: 40 }}>
          <div
            style={{
              display: "flex",
              background: "#000",
              color: "#fff",
              fontSize: 20,
              fontWeight: 700,
              letterSpacing: 1,
              textTransform: "uppercase",
              padding: "12px 32px",
              borderRadius: 6,
            }}
          >
            Ticket {data.index}
          </div>
          {/* eslint-disable-next-line @next/next/no-img-element */}
          <img
            src={qrDataUrl}
            width={320}
            height={320}
            style={{ marginTop: 32, background: "#fff", padding: 20, borderRadius: 16 }}
            alt="QR code"
          />
          <div style={{ display: "flex", fontFamily: "monospace", fontSize: 20, color: "#888", marginTop: 20 }}>
            {data.code}
          </div>
        </div>
      </div>
    ),
    {
      width: 800,
      height: 1120,
      fonts: [
        { name: "Inter", data: interRegular, weight: 400, style: "normal" },
        { name: "Inter", data: interBold, weight: 700, style: "normal" },
      ],
    }
  );

  return Buffer.from(await image.arrayBuffer());
}

// A ticket's card is immutable once issued (same attendee/event/QR forever),
// so it's rendered once and cached to disk under the same persistent
// uploads root organizer-uploaded images use (survives redeploys) --
// without this, every request re-ran font loading + satori layout + QR
// generation (~2.7s cold), which blew past Gmail's image-proxy fetch
// timeout and showed a broken image icon in the actual delivered email even
// though the endpoint worked fine when hit directly. Call this eagerly at
// send time (src/lib/email.ts) so the cache is warm before the email ever
// reaches an inbox, and again from the serving route as a fallback/read path.
export async function getCachedTicketCardPng(data: TicketCardRenderData): Promise<Buffer> {
  const file = cachePath(data.code);
  try {
    return await readFile(file);
  } catch {
    // not cached yet
  }
  const buffer = await renderTicketCardPng(data);
  await mkdir(path.dirname(file), { recursive: true });
  await writeFile(file, buffer);
  return buffer;
}
