import { IIdentifier } from "@veramo/core";
import { getRandomBytesSync } from "ethereum-cryptography/random";
import { entropyToMnemonic } from "ethereum-cryptography/bip39";
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
import { HDNode } from "@ethersproject/hdnode";

import {
  CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
  createEndorserJwtForDid,
  CONTACT_URL_PATH_ENDORSER_CH_OLD,
  CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI,
} from "@/libs/endorserServer";
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
import { decodeEndorserJwt } from "@/libs/crypto/vc";

export const DEFAULT_ROOT_DERIVATION_PATH = "m/84737769'/0'/0'/0'";

export const LOCAL_KMS_NAME = "local";

/**
 *
 *
 * @param {string} address
 * @param {string} publicHex
 * @param {string} privateHex
 * @param {string} derivationPath
 * @return {*}  {Omit<IIdentifier, 'provider'>}
 */
export const newIdentifier = (
  address: string,
  publicHex: string,
  privateHex: string,
  derivationPath: string,
): Omit<IIdentifier, keyof "provider"> => {
  return {
    did: DEFAULT_DID_PROVIDER_NAME + ":" + address,
    keys: [
      {
        kid: publicHex,
        kms: LOCAL_KMS_NAME,
        meta: { derivationPath: derivationPath },
        privateKeyHex: privateHex,
        publicKeyHex: publicHex,
        type: "Secp256k1",
      },
    ],
    provider: DEFAULT_DID_PROVIDER_NAME,
    services: [],
  };
};

/**
 *
 *
 * @param {string} mnemonic
 * @return {*}  {[string, string, string, string]}
 */
export const deriveAddress = (
  mnemonic: string,
  derivationPath: string = DEFAULT_ROOT_DERIVATION_PATH,
): [string, string, string, string] => {
  mnemonic = mnemonic.trim().toLowerCase();

  const hdnode: HDNode = HDNode.fromMnemonic(mnemonic);
  const rootNode: HDNode = hdnode.derivePath(derivationPath);
  const privateHex = rootNode.privateKey.substring(2); // original starts with '0x'
  const publicHex = rootNode.publicKey.substring(2); // original starts with '0x'
  const address = rootNode.address;

  return [address, privateHex, publicHex, derivationPath];
};

export const generateRandomBytes = (numBytes: number): Uint8Array => {
  return getRandomBytesSync(numBytes);
};

/**
 *
 *
 * @return {*}  {string}
 */
export const generateSeed = (): string => {
  const entropy: Uint8Array = getRandomBytesSync(32);
  const mnemonic = entropyToMnemonic(entropy, wordlist);

  return mnemonic;
};

/**
 * Retrieve an access token, or "" if no DID is provided.
 *
 * @return {*}
 */
export const accessToken = async (did?: string) => {
  if (did) {
    const nowEpoch = Math.floor(Date.now() / 1000);
    const endEpoch = nowEpoch + 60; // add one minute
    const tokenPayload = { exp: endEpoch, iat: nowEpoch, iss: did };
    return createEndorserJwtForDid(did, tokenPayload);
  } else {
    return "";
  }
};

/**
 @return payload of JWT pulled out of the URL and decoded:
 { iat: number, iss: string (DID), own: { name, publicEncKey (base64-encoded key) } }

 Result may be a single contact or it may be { contacts: [ contact, ... ] }
 */
export const getContactPayloadFromJwtUrl = (jwtUrlText: string) => {
  let jwtText = jwtUrlText;
  const appImportConfirmUrlLoc = jwtText.indexOf(
    CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
  );
  if (appImportConfirmUrlLoc > -1) {
    jwtText = jwtText.substring(
      appImportConfirmUrlLoc +
        CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI.length,
    );
  }
  const appImportOneUrlLoc = jwtText.indexOf(
    CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI,
  );
  if (appImportOneUrlLoc > -1) {
    jwtText = jwtText.substring(
      appImportOneUrlLoc + CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI.length,
    );
  }
  const endorserUrlPathLoc = jwtText.indexOf(CONTACT_URL_PATH_ENDORSER_CH_OLD);
  if (endorserUrlPathLoc > -1) {
    jwtText = jwtText.substring(
      endorserUrlPathLoc + CONTACT_URL_PATH_ENDORSER_CH_OLD.length,
    );
  }

  // JWT format: { header, payload, signature, data }
  const jwt = decodeEndorserJwt(jwtText);

  return jwt.payload;
};

export const nextDerivationPath = (origDerivPath: string) => {
  let lastStr = origDerivPath.split("/").slice(-1)[0];
  if (lastStr.endsWith("'")) {
    lastStr = lastStr.slice(0, -1);
  }
  const lastNum = parseInt(lastStr, 10);
  const newLastNum = lastNum + 1;
  const newLastStr = newLastNum.toString() + (lastStr.endsWith("'") ? "'" : "");
  const newDerivPath = origDerivPath
    .split("/")
    .slice(0, -1)
    .concat([newLastStr])
    .join("/");
  return newDerivPath;
};