/**
Goal of an identity proof and related procedure is to detect if a peer identity is either just not the person you
know or if MiTM attack is being performed between you using a proxy-keyring. Once the goal is reached and trust is
established between two accounts they can save a secure trust-value for themselves to ensure credibility of each
other moving forward.

Having such a procedure should permanently deter the server from being malicious (MiTM) because the attack is either
prevented or at least becomes detectable at will by the users.

We need robust and convenient ways for the users to perform the identity verification:
    - In-person with apps, using QR-code scanning.
    - Remote live identity code swapping, utilizing an external channel for verification.
    - Remote using passive identity proofs. (prone to misuse by verifier, exploiting party can promote misuse as well)
 */

import {AbstractPeerAccountKeyring, AbstractSelfAccountKeyring} from "../keyring/account_base";
import msgpack from 'msgpack-lite';
import {ACCOUNT_SIGNATURE_CRYPTO_CONTEXT} from "../context/account_signature/__init__";
import {AccountSignatureConfigDumpArgs, AccountSignatureConfigLoadArgs} from "../context/account_signature/args";

export interface passive_identity_signature_type {
  identity: Uint8Array;
  signature: Uint8Array;
}
export interface passive_identity_type {
  account_id: string;
  timestamp: number;
}

/**
    Produce a signed message proving the identity of the self-keyring is bound to its account id.
    It will contain a timestamp, so the verifier can see when it was made relatively.

    If an identified party presents this over any secure or verified channel, they effectively prove ownership
    over their account's identity.

    On its own this is usable to perform a passive identity verification, when the proving user may not
    be available. Users generally should refrain from utilizing this construct directly, because it is easy to
    misuse it without proper identity verification or securing a communication channel.

    We could hash the id value with the salt to make it not apparent, but verifiable. This seems not necessary
    though, because when user use this directly they'll just add their contact id next to it or when it is
    transmitted over a channel it is encrypted anyways.
 *
 * @param self_kr the signing account itself
 * @param timestamp current utc timestamp, should be from synchronized server ping
 */
export async function create_passive_identity_signature(
  self_kr: AbstractSelfAccountKeyring,
  timestamp: number
): Promise<passive_identity_signature_type> {
    const identity = msgpack.encode(
      {
        "v": 1,
        "t": timestamp,
        "s": crypto.getRandomValues(new Uint8Array(16)),
        "i": new TextDecoder().decode(self_kr.id),
      }
    );
    const signature = await ACCOUNT_SIGNATURE_CRYPTO_CONTEXT.dump(new AccountSignatureConfigDumpArgs(identity, self_kr));
    return {
      identity: identity,
      signature: signature,
    };
}

/**
    Warning! This extracts the remote identity of the payload, but no verification is done!

    Only use this if the channel is absolutely secure and identifies the remote party. Then this can be used to
    fetch the peer-kr by account-id, meaning this can facilitate the first-contact of the two accounts. However,
    after the peer keyring is fetched, the signature **must** be verified using verify_passive_identity_signature()
    before trusting it, to actually deny the server the MiTM attack!

    Return a dict containing the account_id and timestamp from the identity data on success, None otherwise.
 *
 * @param identity passive identity payload
 * @param size_limit to not even attempt to process above
 */
export function peek_unverified_passive_identity(
  identity: Uint8Array, size_limit?: number
): void | passive_identity_type {
  if (size_limit === undefined || size_limit === null) {
    size_limit = 1024;
  }
  if (size_limit > 0 && identity.length > size_limit) {
    return;
  }
  let unpacked_identity;
  try {
    unpacked_identity = msgpack.decode(identity);
  } catch {
    return;
  }
  if (unpacked_identity === null || (typeof unpacked_identity !== 'function' && typeof unpacked_identity !== 'object')) {
    return;  // not object, like "isObject"
  }
  if (unpacked_identity.v === 1) {
    const iks = new Set(Object.keys(unpacked_identity));
    if (iks.size != 4
      || !iks.has("v")
      || !iks.has("t")
      || !iks.has("s")
      || !iks.has("i")
    ) {
      return;
    }
    if (
      !Number.isFinite(unpacked_identity.t)
      || !(unpacked_identity.s instanceof Uint8Array)
      || !(typeof unpacked_identity.i === 'string' || unpacked_identity.i instanceof String)
      || unpacked_identity.t < 0
      || unpacked_identity.s.length < 16
      || unpacked_identity.i.length == 0
    ) {
      return;
    }
    return {
      account_id: unpacked_identity.i,
      timestamp: unpacked_identity.t
    }
  }
}

/**
    Verify that a passive identity signature is for the given peer.
    Return a dict containing the account_id and timestamp from the identity data on success, None otherwise.
 *
 * @param peer_kr keyring of the peer that the identity payload is from
 * @param identity passive identity payload
 * @param signature signature of the payload
 */
export async function verify_passive_identity_signature(
  peer_kr: AbstractPeerAccountKeyring, identity: Uint8Array, signature: Uint8Array
): Promise<void | passive_identity_type> {
    try {
      await ACCOUNT_SIGNATURE_CRYPTO_CONTEXT.load(new AccountSignatureConfigLoadArgs(signature, peer_kr, identity));
    } catch {
      return;
    }
    const claim = peek_unverified_passive_identity(identity, 65536);
    if (!!claim && claim['account_id'] !== new TextDecoder().decode(peer_kr.id)) {
      return;
    }
    return claim;
}
