import { AESCipherSIVNonce96 } from '../../base/cipher/aes';
import { AbstractSelfAccountKeyring } from '../../keyring/account_base';
import { PeerAccountKeyring } from '../../keyring/account_peer';
import { AbstractCryptoConfig } from '../base';
import { AccountPairDeriveKeyV1, TS_WINDOW_V1 } from '../common';
import { DialogueConfigDumpArgs, DialogueConfigLoadArgs } from './args';
import { makeInfoByteArray, ValueError } from '../../utility';

/**
 * Dialogue message encryption: AES-SIV
        KEY: 32B from curve25519 scalar_mult -> 64B HKDF by context + current time-slice-window
        NONCE: 12B random

    Key rotation by time is not necessary here by the key usage safety limits, but we do it nevertheless because a
    timestamp is always available and we can limit the blast radius of a single key leak (even if not really relevant).
    NOTE: Large LRU is useful here for pair and given time-window.
 */
export class DialogueConfigV1 extends AbstractCryptoConfig<
  DialogueConfigLoadArgs,
  DialogueConfigDumpArgs
> {
  public static TS_WINDOW = TS_WINDOW_V1;

  constructor(never_dump: boolean = false) {
    super(1, never_dump);
  }

  public load(args: DialogueConfigLoadArgs): Promise<Uint8Array> {
    return new Promise((resolve, reject) => {
      let error;

      let cipher = args.cipher.slice(this.version.byteLength);
      let timestampArr = DialogueConfigV1.TS_WINDOW.windows(args.timestamp);

      let _load = (timestamp_index) => {
        if (timestamp_index >= timestampArr.length) {
          reject(error);
          return;
        }

        let timestamp_window = timestampArr[timestamp_index];

        this.derive_key(args.self_kr, args.peer_kr, timestamp_window)
          .then((derive_key) => {
            AESCipherSIVNonce96.decrypt(cipher, derive_key)
              .then((res) => {
                resolve(res);
                return;
              })
              .catch((err) => {
                if (err instanceof ValueError) {
                  error = err;
                  _load(timestamp_index + 1);
                } else {
                  reject(err);
                }
              });
          })
          .catch(reject);
      };

      _load(0);
    });
  }

  public dump(args: DialogueConfigDumpArgs): Promise<Uint8Array> {
    return this.derive_key(
      args.self_kr,
      args.peer_kr,
      DialogueConfigV1.TS_WINDOW.window(args.timestamp)
    ).then((derive_key) => {
      return AESCipherSIVNonce96.encrypt(
        args.plain,
        derive_key,
        undefined,
        undefined,
        this.version
      );
    });
  }

  public derive_key(
    self_kr: AbstractSelfAccountKeyring,
    peer_kr: PeerAccountKeyring,
    ts_window: Uint8Array
  ) {
    return AccountPairDeriveKeyV1.derive_key(
      self_kr,
      peer_kr,
      makeInfoByteArray(['dialogue-', this.version, '-', ts_window])
    );
  }
}
