/// <reference types="@fastly/js-compute" />
import * as crypto from 'crypto-js';
import { formatISO } from 'date-fns';
import { ConfigStore } from "fastly:config-store";

const MS_API_VERSION = "2017-11-09";

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

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

  // Only generate authorize header for GET and HEAD request
  // Pass PURGE to backend as it is
  // Block other kinds of method
  if (req.method === "GET" || req.method === "HEAD") {
    authorizeRequest(req);

    let res = await fetch(req, {
      backend: "azure_backend",
    });

    // Remove MS headers returned from Azure Blob
    res.headers.delete("x-ms-request-id");
    res.headers.delete("x-ms-blob-type");
    res.headers.delete("x-ms-server-encrypted");
    res.headers.delete("x-ms-version");
    res.headers.delete("x-ms-creation-time");
    res.headers.delete("x-ms-lease-status");
    res.headers.delete("x-ms-lease-state");
    res.headers.delete("content-md5");
    res.headers.delete("server");

    return res;
  } else if (req.method === "PURGE") {
    // When doing the purge, we need to make sure the cache key matches
    // Cache key is cacualted from URL and HOST header of request sent to backend
    // URL of backend request has no query string
    req.headers.set("host", getConfig().HOST);

    return await fetch(req, {
      backend: "azure_backend",
    });
  } else {
    return new Response("This method is not allowed", { status: 405 });
  }
}

function authorizeRequest(req) {
  const cfg = getConfig();
  // The timestamp format should be something like
  // Fri, 01 Jan 2021 00:14:22 GMT
  const timestamp = new Date().toUTCString();

  // Decode base64 format access key to binary key
  const decodedKey = crypto.enc.Base64.parse(cfg.ACCOUNT_KEY);

  // Canonical headers must be sorted and concatenated with newlines.
  // If there are query params there is a separate spec for that, not supported here yet
  const canonicalizedHeaders = `x-ms-date:${timestamp}\nx-ms-version:${MS_API_VERSION}\n`;

  // Canonical Resource is /account/cotainer/object
  const url = new URL(req.url);
  const canonicalizedResource = `/${cfg.ACCOUNT_NAME}${url.pathname}`;
  console.log(`canonicalizedResource = ${canonicalizedResource}`);

  // Construct everything properly before signing
  // We are adding 4 newlines here, however the spec says we
  // can override any of these 4 headers with values if we want.
  // For now we are just blanking them out
  const stringToSign = `GET\n\n\n\n${canonicalizedHeaders}${canonicalizedResource}`;
  // HMAC-sign with SHA256 and Base64-encode the result
  const signature_binary = hmacSha256(decodedKey, stringToSign);
  const signature = crypto.enc.Base64.stringify(signature_binary);

  const authorization = `SharedKeyLite ${cfg.ACCOUNT_NAME}:${signature}`;

  req.headers.set("host", cfg.HOST);
  req.headers.set("authorization", authorization);
  req.headers.set("x-ms-date", timestamp);
  req.headers.set("x-ms-version", MS_API_VERSION);

  console.log(`Path: ${url.pathname}, Authorization: ${authorization}`);
}

function fixupURL(req) {
  let url = new URL(req.url);
  // Ignore the query string from client
  url.search = "";
  // Request path for azure storage is /container/object
  url.pathname = `/${getConfig().BLOB_CONTAINER_NAME}${url.pathname}`;

  return new Request(url, req);
}

function hmacSha256(signingKey, stringToSign) {
  return crypto.HmacSHA256(stringToSign, signingKey, { asBytes: true });
}

function getConfig() {
  const config = new ConfigStore('azure_config');
  return {
    ACCOUNT_NAME: config.get("account_name"),
    ACCOUNT_KEY: config.get("account_key"),
    HOST: `${config.get("account_name")}.blob.core.windows.net`,
    BLOB_CONTAINER_NAME: config.get("blob_container_name")
  };
}