/**
 * /api/outfitPageSSR also serves the initial request for /outfits/:id, to
 * add title and meta tags. This primarily for sharing, like on Discord or
 * Twitter or Facebook!
 *
 * The route is configured in vercel.json, at the project root.
 *
 * To be honest, we probably should have built Impress 2020 on Next.js, and
 * then we'd be getting realistic server-side rendering across practically the
 * whole app very cheaply. But this is a good hack for what we have!
 *
 * TODO: We could add the basic outfit page layout and image preview, to use
 *       SSR to decrease time-to-first-content for the end-user, too…
 */
const beeline = require("honeycomb-beeline")({
  writeKey: process.env["HONEYCOMB_WRITE_KEY"],
  dataset:
    process.env["NODE_ENV"] === "production"
      ? "Dress to Impress (2020)"
      : "Dress to Impress (2020, dev)",
  serviceName: "impress-2020-gql-server",
});

import escapeHtml from "escape-html";
import fetch from "node-fetch";

import connectToDb from "../src/server/db";
import { normalizeRow } from "../src/server/util";

async function handle(req, res) {
  // Load index.html as our initial page content. If this fails, it probably
  // means something is misconfigured in a big way; we don't have a great way
  // to recover, and we'll just show an error message.
  let initialHtml;
  try {
    initialHtml = await loadIndexPageHtml();
  } catch (e) {
    console.error("Error loading index.html:", e);
    return reject(res, "Sorry, there was an error loading this outfit page!");
  }

  // Load the given outfit by ID. If this fails, it's possible that it's just a
  // problem with the SSR, and the client will be able to handle it better
  // anyway, so just show the standard index.html and let the app load
  // normally, as if there was no error. (We'll just log it.)
  let outfit;
  try {
    outfit = await loadOutfitData(req.query.id);
  } catch (e) {
    console.error("Error loading outfit data:", e);
    return sendHtml(res, initialHtml, 200);
  }

  // Similarly, if the outfit isn't found, we just show index.html - but with a
  // 404 and a gentler log message.
  if (outfit == null) {
    console.info(`Outfit not found: ${req.query.id}`);
    return sendHtml(res, initialHtml, 404);
  }

  const outfitName = outfit.name || "Untitled outfit";

  // Okay, now let's rewrite the HTML to include some outfit data!
  //
  // WARNING!!!
  //     Be sure to always use `escapeHtml` when inserting user data!!
  // WARNING!!!
  //
  let html = initialHtml;

  // Add the outfit name to the title.
  html = html.replace(
    /<title>(.*)<\/title>/,
    `<title>${escapeHtml(outfitName)} | Dress to Impress</title>`
  );

  // Add sharing meta tags just before the </head> tag.
  const updatedAtTimestamp = Math.floor(
    new Date(outfit.updatedAt).getTime() / 1000
  );
  const outfitUrl =
    `https://impress-2020.openneo.net/outfits` +
    `/${encodeURIComponent(outfit.id)}`;
  const imageUrl =
    `https://impress-outfit-images.openneo.net/outfits` +
    `/${encodeURIComponent(outfit.id)}` +
    `/v/${encodeURIComponent(updatedAtTimestamp)}` +
    `/600.png`;
  const metaTags = `
    <meta property="og:title" content="${escapeHtml(outfitName)}">
    <meta property="og:type" content="website">
    <meta property="og:image" content="${escapeHtml(imageUrl)}">
    <meta property="og:url" content="${escapeHtml(outfitUrl)}">
    <meta property="og:site_name" content="Dress to Impress">
    <meta property="og:description" content="A custom Neopets outfit, designed on Dress to Impress!">
  `;
  html = html.replace(/<\/head>/, `${metaTags}</head>`);

  console.info(`Successfully SSR'd outfit ${outfit.id}`);

  return sendHtml(res, html);
}

async function loadOutfitData(id) {
  const db = await connectToDb();
  const [rows] = await db.query(`SELECT * FROM outfits WHERE id = ?;`, [id]);
  if (rows.length === 0) {
    return null;
  }

  return normalizeRow(rows[0]);
}

let cachedIndexPageHtml = null;
async function loadIndexPageHtml() {
  if (cachedIndexPageHtml == null) {
    // Request the same built copy of index.html that we're already serving at
    // our homepage.
    const homepageUrl = process.env.VERCEL_URL
      ? `https://${process.env.VERCEL_URL}/`
      : process.env.NODE_ENV === "development"
      ? "http://localhost:3000/"
      : "https://impress-2020.openneo.net/";
    const liveIndexPageHtml = await fetch(homepageUrl).then((res) =>
      res.text()
    );
    cachedIndexPageHtml = liveIndexPageHtml;
  }

  return cachedIndexPageHtml;
}

function reject(res, message, status = 400) {
  res.setHeader("Content-Type", "text/plain");
  return res.status(status).send(message);
}

function sendHtml(res, html, status = 200) {
  res.setHeader("Content-Type", "text/html");
  return res.status(status).send(html);
}

async function handleWithBeeline(req, res) {
  beeline.withTrace(
    { name: "api/outfitPageSSR", operation_name: "api/outfitPageSSR" },
    () => handle(req, res)
  );
}

export default handleWithBeeline;