const secret = "THESECRET";
const validCenturyLinkPattern = new RegExp(
  "/token=nva=(\\d+)~dirs=(\\d+)~hash=0([\\da-f]{20})(/.*)"
);
const validHash = new RegExp("[\\da-f]{20}");

async function verifyToken(req) {
  const reqUrl = new URL(req.url);
  const pathname = reqUrl.pathname;
  const urlBasename = reqUrl.pathname.split(/[\\/]/).pop();

  let nva;
  let dirs;
  let hash;
  let path;
  let expectedNumOfDirs;
  let expectedHash;

  if (/(playlist.m3u8)|(manifest.mpd)/.test(urlBasename)) {
    if (validCenturyLinkPattern.test(pathname)) {
      const centuryLinkSegments = pathname.match(validCenturyLinkPattern);
      nva = centuryLinkSegments[1];
      dirs = centuryLinkSegments[2];
      hash = centuryLinkSegments[3];
      path = centuryLinkSegments[4];

      console.debug(`nva=${nva}; dirs=${dirs}; hash=${hash}; path=${path}`);
    } else {
      console.warn("Missing 'token' in URL path");
      return new Response("Missing 'token' in URL path", {
        status: 403,
        statusText: "Forbidden"
      });
    }

    const tokenAggregate = `${path}?nva=${nva}&dirs=${dirs}`;
    console.debug(`token=${tokenAggregate}`);

    // Validate the number of directories
    expectedNumOfDirs = path.match(/\//g).length - 1;
    console.debug(`expectedNumOfDirs=${expectedNumOfDirs} | dirs=${dirs}`);
    if (dirs != expectedNumOfDirs) {
      console.warn("Expected number of directories is invalid.");
      return new Response("Expected number of directories is invalid.", {
        status: 403,
        statusText: "Forbidden"
      });
    }

    // Extract token expiration and signature
    const utf8Encoder = new TextEncoder();
    const crypto = require("crypto");
    const hmacSha1 = crypto.createHmac("sha1", utf8Encoder.encode(secret));
    const digest = hmacSha1.update(tokenAggregate, "utf8").digest("hex");
    console.debug(`digest: ${digest}`);
    expectedHash = digest.match(validHash);

    if (expectedHash.length == 1) {
      console.debug(`expectedHash: ${expectedHash[0]}`);
      if (expectedHash == hash) {
        // Check that expiration time has not elapsed
        const normalizedTimeInMsToCompare = new Date(parseInt(nva + "000"));
        if (normalizedTimeInMsToCompare < Date.now()) {
          console.warn("Token has expired");
          return new Response("Token has expired", {
            status: 403,
            statusText: "Forbidden"
          });
        }
      } else {
        console.warn("Token is incorrect, expected: " + expectedHash);
        return new Response("Token is incorrect", {
          status: 403,
          statusText: "Forbidden"
        });
      }
    }
    console.info("Token is good");
  }

  console.info(`${pathname} ${urlBasename}`);
  return fetch(req, {
    backend: "origin_0"
  });
}

async function handleRequest(event) {
  const req = event.request;

  return verifyToken(req);
}

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));