import {
    Html5QrcodeScanner,
    Html5QrcodeSupportedFormats
} from "html5-qrcode";
import {
    ed448
} from "@noble/curves/ed448";
import {
    Buffer
} from "buffer";

var currentlyAddingItemByTag = new Map();
var received_items = {};
var config;
var scanner = new Html5QrcodeScanner("reader",{
  fps: 10,
  qrbox: { width: 250, height: 250 },
  showTorchButtonIfSupported: true,
  formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ], //there are many other types of codes on the labels we do not want
  rememberLastUsedCamera: true,
});
var scanner_audio = new Audio("/beep.wav");
scanner_audio.load();

/*
 * Helper functions
 */
function htoa(h) {
  return Uint8Array.from(Buffer.from(h,"hex"));
}

async function fetchWithAuth(input, init) {
  if(init == undefined || init == null) {
    init = {};
  }
  if(init["headers"] == null) {
    init["headers"] = {};
  }

  if(!window.sessionStorage.getItem("access_token")) {
    await login();
  }

  init["headers"]["Authorization"] = "Bearer " + window.sessionStorage.getItem("access_token");
  let response = await fetch(input, init);

  while(response.status == 401) {
    console.log("Fetch with auth failed for", input);
    await login();
    init["headers"]["Authorization"] = "Bearer " + window.sessionStorage.getItem("access_token");
    response = await fetch(input, init);
  }

  return response;
}

async function checkResponseForError(response, message) {
  if (response.status !== 200) {
    let content = await response.text();
    console.log("Got Response Error:", message, response);
    alert(message + "; Status: " + response.status + "; Content: " + content);
    return false;
  }
  return true;
}

async function getConfig() {
  if (config == undefined) {
    var config_request = await fetch("config.json");
    console.log("Loading config");
    if(await checkResponseForError(config_request, "Could not load config")) {
      config = await config_request.json();
      console.log("Loaded config");
    }
  }
}

async function login() {
  console.log("Performing login");
  let loginSuccess = false;
  do {
    console.log("Prompting user for shared secret.");
    var password = prompt("provide the shared secret");
    var form = new FormData();
    form.append("grant_type", "password");
    form.append("username", "worker");
    form.append("password", password);
    console.log("Requesting token.");
    var response = await fetch(config.backend_url + "/token", {
      method: "POST",
      body: form,
    });
    if (await checkResponseForError(response, "Login failed")) {
      var result = await response.json();
      console.log("Got token");
      window.sessionStorage.setItem("access_token", result.access_token);
      loginSuccess = true;
    }
  } while(!loginSuccess);
}


function updateItemsView() {
  console.log("Updating items view");
  var items = document.querySelector("#items");
  items.innerHTML = "";
  for (var item in received_items) {
    var li = document.createElement("li");
    li.innerHTML = "<div id=\"item_" + received_items[item].item.uuid + "\" class=\"alert alert-success\" role=\"alert\">" +
                  received_items[item].item.tag + " (" + received_items[item].item.addressee + ", " + received_items[item].item.team + ")" +
                  "&emsp;<span class=\"badge badge-light\">"+received_items[item].num+"</span>"+
                  "&emsp;<button class=\"btn btn-primary\" onclick=\"lib.increaseItemCount('" + received_items[item].item.uuid + "')\">+</button>" +
                  "&emsp;<button class=\"btn btn-primary\" onclick=\"lib.decreaseItemCount('" + received_items[item].item.uuid + "')\">-</button></div>";
    items.appendChild(li);
  }
}

/*
 * various add Items functions
 */

export async function onTagTextUpdate(element) {
  // A character was typed
  if(element.value.length == 0) {
    element.setCustomValidity("");
    element.classList.remove("is-invalid");
  }
  else if(element.value.length != 6) {
    element.setCustomValidity("Code must be 6 chars long.");
    element.classList.add("is-invalid");
    return;
  } else {
    tagEntered(element);
  }
}

export async function onTagInputChanged(element) {
  // Element lost focus or user pressed enter
  if(element.value.length == 6) {
    tagEntered(element);
  } else {
    element.reportValidity();
  }
}

async function tagEntered(element) {
  let tag = element.value;
  console.log("Tag Entered", tag);
  if(currentlyAddingItemByTag.has(tag)) {
    return;
  }
  currentlyAddingItemByTag.set(tag, "");
  let succ = await addItemByTag(element.value);
  if(succ) {
    element.classList.remove("is-invalid");
    element.value = "";
    element.setCustomValidity("");
  } else {
    element.setCustomValidity("Unknown item");
    element.classList.add("is-invalid");
  }
  element.reportValidity();
  currentlyAddingItemByTag.delete(tag);
}

export async function addItemByTag(tag) {
  if (tag.length != 6) {
    throw "Internal Error: Tag not length 6.";
  }

  tag = tag.toLowerCase();

  console.log("Trying to add item by tag:", tag);

  var response = await fetchWithAuth(config.backend_url + "/tag/" + tag);
  if (response.status == 200) {
    var tracking_item = await response.json();
    if (tracking_item.uuid in received_items) {
      received_items[tracking_item.uuid].num += 1;
    } else {
      received_items[tracking_item.uuid] = { "num":1, "item": tracking_item };
    }
    updateItemsView();
    scanner_audio.play();
    console.log("Added item by tag");
    return true;
  } else if(response.status == 404) {
    console.log("Item not found");
    return false;
  } else {
    checkResponseForError(response, "Failed to get item from backend");
    return false;
  }
}

async function handleQrError(msg, err){
  if(err.type == 2 /*Not found*/ || err.errorMessage.includes("error = NotFoundException")) {
    return;
  }
  alert("Qrcode Error: " + msg + " (" + err + ")");
  console.log("Qrcode Error: ", msg, err);
}

async function handleItemScanResult(item_uuid) {
  if (item_uuid.length == 36) {
    console.log("Trying to add item by uuid (scanned)");
    if (item_uuid in received_items) {
      received_items[item_uuid].num += 1;
      console.log("Increased count of existing item uuid");
      updateItemsView();
      scanner_audio.play();
    } else {
      received_items[item_uuid] = { "num":1, "item": null };
      var response = await fetch(config.backend_url + "/item/"+item_uuid);
      if (response.status == 200) {
        var tracking_item = await response.json();
        received_items[item_uuid] = { "num":1, "item": tracking_item };
        updateItemsView();
        scanner_audio.play();
        console.log("Added item by uuid.");
      } else if(response.status == 404) {
        alert("Item not found");
        console.log("Item not found");
      } else {
        checkResponseForError(response, "Failed to get item from backend");
      }
    }
  } else {
    alert("QR Code is not a bgp checkin code.");
  }
}

export async function addItemScan() {
  console.log("Starting scanner");
  scanner.render(handleItemScanResult, handleQrError);
  document.getElementById("startScan").style.display = "none";
  document.getElementById("stopScan").style.display = "inline-block";
}

export async function stopScanningItem() {
  console.log("Stopping scanner");
  scanner.clear();
  document.getElementById("startScan").style.display = "inline-block";
  document.getElementById("stopScan").style.display = "none";
}

export async function addItemImage(e) {
  console.log("Adding item  by image");
  var form = new FormData();
  form.append("image", e.files[0]);
  var response = await fetchWithAuth(config.backend_url + "/item/register", {
    method: "POST",
    body: form
  });
  if(await checkResponseForError(response, "Failed to save item")) {
    var tracking_item = await response.json();
    received_items[tracking_item.uuid] = {"num": 1, "item": tracking_item};
    updateItemsView();
    scanner_audio.play();
    console.log("Added item by image.");
  }
}

export async function decreaseItemCount(uuid) {
  if(received_items[uuid] == null) {
    console.log("Trying to decrease item count of unknown item", uuid);
    return;
  }
  console.log("Decreasing item count of", uuid);
  received_items[uuid]["num"] --;
  if(received_items[uuid]["num"] < 0) {
    received_items[uuid]["num"] = 0;
  }
  updateItemsView();
}

export async function increaseItemCount(uuid) {
  if(received_items[uuid] == null) {
    console.log("Trying to increase item count of unknown item", uuid);
    return;
  }
  console.log("Increasing item count of", uuid);
  received_items[uuid]["num"] ++;
  updateItemsView();
}

/*
 * Checkin the selected items
 */
export async function checkinItems() {
  console.log("Checking in items");
  for (var item in received_items) {
    var response = await fetchWithAuth(config.backend_url + "/checkin", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            "item_uuid": received_items[item].item.uuid,
            "storage_name": document.querySelector("#storage_select").value,
            "num": received_items[item].num
        })
    });
    if(await checkResponseForError(response, "Failed to checkin item")) {
      var tracking_item = await response.json(); // eslint-disable-line no-unused-vars
    }
  }
  received_items = {};
  updateItemsView();
}

async function onRecevierScanSuccess(scannedCode) {
  var [uuid,signature] = scannedCode.split("/");
  console.log("Receiver scan success");
  scanner_audio.play();
  await getConfig();
  var response = await fetch(config.backend_url + "/item/"+uuid);
  if (response.status == 200) {
    var tracking_item = await response.json();
    if (ed448.verify(htoa(signature),new TextEncoder().encode(tracking_item.uuid),htoa(tracking_item.verification))) {
      document.querySelector("#uuid").innerHTML = tracking_item.uuid;
      document.querySelector("#tag").innerHTML = tracking_item.tag;
      document.querySelector("#storage").innerHTML = tracking_item.storage;
      document.querySelector("#addressee").innerHTML = tracking_item.addressee;
      document.querySelector("#team").innerHTML = tracking_item.team;
      console.log("Receiver verify success");
    } else {
      alert("Could not verify signature.");
      console.log("Could not verify signature.");
    }
  } else if(response.status == 404) {
    alert("Item not found.");
  } else {
    checkResponseForError(response, "Failed to get item from backend.");
  }
}

export async function scanReceiver() {
  console.log("Scanning receiver.");
  scanner.render(onRecevierScanSuccess, handleQrError);
}


export async function loadStorages(selectedStorage) {
  await getConfig();
  console.log("Retrieving storages");
  var response = await fetchWithAuth(config.backend_url + "/storages");
  if(await checkResponseForError(response, "Failed to load storages.")) {
    var storages = await response.json();
    var select = document.querySelector("#storage_select");
    console.log("Got storages");
    for (var i in storages) {
      var option = document.createElement("option");
      option.textContent = storages[i].name;
      option.value = storages[i].name;
      select.appendChild(option);
      if (selectedStorage == storages[i].name) {
        select.value = selectedStorage;
      }
    }
  }
}