function arrBufToB64Url(buf: ArrayBuffer) {
  const b64data = btoa(String.fromCharCode(...new Uint8Array(buf)));
  return b64data.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}

function stringToBuffer(str: string) {
  const buffer = new ArrayBuffer(str.length);
  const ui8 = new Uint8Array(buffer);
  for (let i = 0, bufLen = str.length; i < bufLen; i++) {
    ui8[i] = str.charCodeAt(i);
  }
  return buffer;
}

export async function GenerateUploadURL(
  env: Env,
  path: string,
  size: number,
  fileExt: string
): Promise<string> {
  const accessToken = await GetAccessToken(env);
  const contentTypes: { [k: string]: string } = {
    gif: "image/gif",
    heic: "image/heic",
    heif: "image/heif",
    jfif: "image/jpeg",
    jpeg: "image/jpeg",
    jpg: "image/jpeg",
    m4v: "video/x-m4v",
    mkv: "video/x-matroska",
    mov: "video/quicktime",
    mp4: "video/mp4",
    png: "image/png",
    webp: "image/webp",
    webm: "video/webm",
    wmv: "video/x-ms-wmv",
  };

  const resumableUploadReq = await fetch(
    `https://storage.googleapis.com/upload/storage/v1/b/portal-carcrushers-cc/o?uploadType=resumable&name=${encodeURIComponent(
      path
    )}`,
    {
      headers: {
        authorization: `Bearer ${accessToken}`,
        "x-upload-content-type": contentTypes[fileExt],
        "x-upload-content-length": size.toString(),
      },
      method: "POST",
    }
  );

  if (!resumableUploadReq.ok)
    throw new Error(
      `Failed to create resumable upload: ${await resumableUploadReq.text()}`
    );

  const url = resumableUploadReq.headers.get("location");

  if (!url) throw new Error("No location header returned");

  return url;
}

async function GetAccessToken(env: Env): Promise<string> {
  const claimSet = btoa(
    JSON.stringify({
      aud: "https://oauth2.googleapis.com/token",
      exp: Math.floor(Date.now() / 1000) + 120,
      iat: Math.floor(Date.now() / 1000),
      iss: env.WORKER_GSERVICEACCOUNT,
      scope:
        "https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/devstorage.read_write",
    })
  )
    .replaceAll("+", "-")
    .replaceAll("/", "_")
    .replaceAll("=", "");

  const signingKey = await crypto.subtle.importKey(
    "pkcs8",
    stringToBuffer(
      atob(env.STORAGE_ACCOUNT_KEY.replace(/(\r\n|\n|\r)/gm, ""))
    ),
    { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
    false,
    ["sign"]
  );
  const signature = await crypto.subtle.sign(
    { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
    signingKey,
    stringToBuffer(`eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.${claimSet}`)
  );
  const assertion = `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.${claimSet}.${arrBufToB64Url(
    signature
  )}`;
  const tokenRequest = await fetch("https://oauth2.googleapis.com/token", {
    body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${assertion}`,
    headers: {
      "content-type": "application/x-www-form-urlencoded",
    },
    method: "POST",
  });

  if (!tokenRequest.ok)
    throw new Error(`Failed to get access token: ${await tokenRequest.text()}`);

  return ((await tokenRequest.json()) as { [k: string]: any }).access_token;
}