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

/**
 * Nano message (request and initial response) encryption: AES-SIV
        KEY: 32B from curve25519 scalar_mult -> 64B HKDF by context + current time-slice-window
        NONCE: 12B random
 */
export class NanoConfigV1 extends AbstractCryptoConfig<NanoConfigLoadArgs, NanoConfigDumpArgs> {
  public static TS_WINDOW = TS_WINDOW_V1;

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

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

      let cipher = args.cipher.slice(this.version.byteLength);
      let timestampArr = NanoConfigV1.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,
          args.room_kr.get_key(AbstractRoomKeyring.PINNED),
          timestamp_window
        )
          .then((derive_key) => {
            AESCipherSIVNonce96.decrypt(cipher, derive_key)
              .then((res) => {
                resolve(res);
              })
              .catch((err) => {
                if (err instanceof ValueError) {
                  error = err;
                  _load(timestamp_index + 1);
                } else {
                  reject(err);
                }
              });
          })
          .catch(reject);
      };

      _load(0);
    });
  }

  public load2(args: NanoConfigLoadArgs): Promise<Uint8Array> {
    let cipher = args.cipher.slice(this.version.byteLength);
    for (let timestamp_window in NanoConfigV1.TS_WINDOW.windows(args.timestamp)) {
      return this.derive_key(
        args.self_kr,
        args.peer_kr,
        args.room_kr.get_key(AbstractRoomKeyring.PINNED),
        new TextEncoder().encode(timestamp_window)
      ).then((derive_key) => {
        return AESCipherSIVNonce96.decrypt(cipher, derive_key);
      });
    }

    throw 'error';
  }

  public dump(args: NanoConfigDumpArgs): Promise<Uint8Array> {
    return this.derive_key(
      args.self_kr,
      args.peer_kr,
      args.room_kr.get_key(AbstractRoomKeyring.PINNED),
      NanoConfigV1.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,
    room_secret: Uint8Array,
    ts_window: Uint8Array
  ) {
    return AccountPairDeriveKeyV1.derive_key(
      self_kr,
      peer_kr,
      makeInfoByteArray(['nano-', this.version, '-', room_secret, '-', ts_window])
    );
  }
}
