import { IIdentifier } from "@veramo/core";
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
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 * as didJwt from "did-jwt";
import { Signer } from "did-jwt";
import * as u8a from "uint8arrays";

export function hexToBytes(s: string): Uint8Array {
  const input = s.startsWith("0x") ? s.substring(2) : s;
  return u8a.fromString(input.toLowerCase(), "base16");
}

export function fromJose(signature: string): {
  r: string;
  s: string;
  recoveryParam?: number;
} {
  const signatureBytes: Uint8Array = base64ToBytes(signature);
  if (signatureBytes.length < 64 || signatureBytes.length > 65) {
    throw new TypeError(
      `Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}`
    );
  }
  const r = bytesToHex(signatureBytes.slice(0, 32));
  const s = bytesToHex(signatureBytes.slice(32, 64));
  const recoveryParam =
    signatureBytes.length === 65 ? signatureBytes[64] : undefined;
  return { r, s, recoveryParam };
}

export function bytesToHex(b: Uint8Array): string {
  return u8a.toString(b, "base16");
}

export function base64ToBytes(s: string): Uint8Array {
  const inputBase64Url = s
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=/g, "");
  return u8a.fromString(inputBase64Url, "base64url");
}

/**
 *
 *
 * @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",
        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
): [string, string, string, string] => {
  const UPORT_ROOT_DERIVATION_PATH = "m/7696500'/0'/0'/0'";
  mnemonic = mnemonic.trim().toLowerCase();

  const hdnode: HDNode = HDNode.fromMnemonic(mnemonic);
  const rootNode: HDNode = hdnode.derivePath(UPORT_ROOT_DERIVATION_PATH);
  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, UPORT_ROOT_DERIVATION_PATH];
};

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

  return mnemonic;
};

/**
 * Retreive an access token
 *
 * @param {IIdentifier} identifier
 * @return {*}
 */
export const accessToken = async (identifier: IIdentifier) => {
  const did: string = identifier.did;
  const privateKeyHex: string = identifier.keys[0].privateKeyHex as string;
  const input = privateKeyHex.startsWith("0x")
    ? privateKeyHex.substring(2)
    : privateKeyHex;
  const privateKeyBytes = u8a.fromString(input.toLowerCase(), "base16");

  const signer = didJwt.SimpleSigner(privateKeyHex);

  const nowEpoch = Math.floor(Date.now() / 1000);
  const endEpoch = nowEpoch + 60; // add one minute

  const uportTokenPayload = { exp: endEpoch, iat: nowEpoch, iss: did };
  const alg = undefined; // defaults to 'ES256K', more standardized but harder to verify vs ES256K-R
  const jwt: string = await didJwt.createJWT(uportTokenPayload, {
    alg,
    issuer: did,
    signer,
  });
  return jwt;
};

export const sign = async (privateKeyHex: string) => {
  const input = privateKeyHex.startsWith("0x")
    ? privateKeyHex.substring(2)
    : privateKeyHex;
  const privateKeyBytes = u8a.fromString(input.toLowerCase(), "base16");

  const signer = didJwt.SimpleSigner(privateKeyHex);

  return signer;
};

/**
 *  The SimpleSigner returns a configured function for signing data.
 *
 *  @example
 *  const signer = SimpleSigner(process.env.PRIVATE_KEY)
 *  signer(data, (err, signature) => {
 *    ...
 *  })
 *
 *  @param    {String}         hexPrivateKey    a hex encoded private key
 *  @return   {Function}                     a configured signer function
 */
export const SimpleSigner = async (hexPrivateKey: string): Promise<Signer> => {
  const signer = didJwt.ES256KSigner(hexToBytes(hexPrivateKey), true);
  return async (data) => {
    const signature = (await signer(data)) as string;
    return fromJose(signature);
  };
};