import { AESCipherSIVNonce96 } from '../../base/cipher/aes';
import { AbstractCryptoConfig } from '../base';
import { NanoOwnConfigDumpArgs, NanoOwnConfigLoadArgs } from './args';
import { AccountMasterDeriveKeyV1, TS_WINDOW_V1 } from '../common';
import { AbstractSelfAccountKeyring } from '../../keyring/account_base';
import { makeInfoByteArray, ValueError } from '../../utility';

/**
 * Nano message (request and initial response) encryption: AES-SIV
        KEY: 32B master secret from self kr -> 64B HKDF by context + current time-slice-window
        NONCE: 12B random
 */
export class NanoOwnConfigV1 extends AbstractCryptoConfig<
  NanoOwnConfigLoadArgs,
  NanoOwnConfigDumpArgs
> {
  public static TS_WINDOW = TS_WINDOW_V1;

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

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

      let cipher = args.cipher.slice(this.version.byteLength);
      let timestampArr = NanoOwnConfigV1.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, 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 dump(args: NanoOwnConfigDumpArgs): Promise<Uint8Array> {
    return this.derive_key(args.self_kr, NanoOwnConfigV1.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, ts_window: Uint8Array) {
    return AccountMasterDeriveKeyV1.derive_key(
      self_kr,
      makeInfoByteArray(['nano-own-', this.version, '-', ts_window])
    );
  }
}
