import { TestBed } from '@angular/core/testing';
import { HttpClient, HttpClientModule, HttpHeaders } from '@angular/common/http';
import { CookieModule, CookieService } from 'ngx-cookie';
import { base64 } from 'rfc4648';

import msgpack from 'msgpack-lite';

import { AccountConfigDumpArgs, AccountConfigLoadArgs } from './context/account/args';
import { SelfAccountKeyring } from './keyring/account_self';
import { AbstractPeerAccountKeyring, AbstractSelfAccountKeyring } from './keyring/account_base';
import { AbstractKeyring } from './keyring/base';
import { AbstractPrivateKeyring, PrivateKeyringV1 } from './keyring/private';
import { AbstractRoomKeyring } from './keyring/room_base';
import { AccountMasterDeriveKeyV1 } from './context/common';
import { RoomKeyring } from './keyring/room';
import {
  AccountTrustConfigDumpArgs,
  AccountTrustConfigLoadArgs,
} from './context/account_trust/args';
import {
  AccountRoomKeyConfigDumpArgs,
  AccountRoomKeyConfigLoadArgs,
} from './context/account_key/room/args';
import {
  AccountRootKeyConfigDumpArgs,
  AccountRootKeyConfigLoadArgs,
} from './context/account_key/root/args';
import {
  AccountSignatureConfigDumpArgs,
  AccountSignatureConfigLoadArgs,
} from './context/account_signature/args';
import { DialogueConfigDumpArgs, DialogueConfigLoadArgs } from './context/dialogue/args';
import { TimestampWindowCalculator } from './context/timestamp';
import { NanoConfigDumpArgs, NanoConfigLoadArgs } from './context/nano/args';
import { NanoDataConfigDumpArgs, NanoDataConfigLoadArgs } from './context/nano_data/args';
import { NanoOwnConfigDumpArgs, NanoOwnConfigLoadArgs } from './context/nano_own/args';
import { PrivateKeyConfigDumpArgs, PrivateKeyConfigLoadArgs } from './context/private_key/args';
import { RoomConfigDumpArgs, RoomConfigLoadArgs } from './context/room/args';
import { RoomKeyConfigDumpArgs, RoomKeyConfigLoadArgs } from './context/room_key/args';
import { environment } from '../../../environments/environment';
import { ACCOUNT_SIGNATURE_CRYPTO_CONTEXT } from './context/account_signature/__init__';
import { startswithByteArray } from './utility';
import { AbstractConfigDumpArgs, AbstractConfigLoadArgs, CryptoContext } from './context/base';
import { DIALOGUE_CRYPTO_CONTEXT } from './context/dialogue/__init__';
import { PeerAccountKeyring } from './keyring/account_peer';
import { NANO_CRYPTO_CONTEXT } from './context/nano/__init__';
import { NANO_DATA_CRYPTO_CONTEXT } from './context/nano_data/__init__';
import { NANO_OWN_CRYPTO_CONTEXT } from './context/nano_own/__init__';
import { ROOM_CRYPTO_CONTEXT } from './context/room/__init__';
import { ACCOUNT_CRYPTO_CONTEXT } from './context/account/__init__';
import { ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT } from './context/account_key/room/__init__';
import { ACCOUNT_ROOT_KEY_CRYPTO_CONTEXT } from './context/account_key/root/__init__';
import { ACCOUNT_TRUST_CRYPTO_CONTEXT } from './context/account_trust/__init__';
import { PRIVATE_KEY_CRYPTO_CONTEXT } from './context/private_key/__init__';
import { ROOM_KEY_CRYPTO_CONTEXT } from './context/room_key/__init__';
import { LOGIN_CHALLENGE_CRYPTO_CONTEXT } from './context/login_challenge/__init__';
import {
  LoginChallengeConfigDumpArgs,
  LoginChallengeConfigLoadArgs,
} from './context/login_challenge/args';
import { REGISTER_CHALLENGE_CRYPTO_CONTEXT } from './context/register_challenge/__init__';
import {
  RegisterChallengeConfigDumpArgs,
  RegisterChallengeConfigLoadArgs,
} from './context/register_challenge/args';
import { LOCAL_STORAGE_CRYPTO_CONTEXT } from './context/local_storage/__init__';
import {
  LocalStorageConfigDumpArgs,
  LocalStorageConfigLoadArgs,
} from './context/local_storage/args';

// only for test keyrings ------------------------------------------------
let TYPE_MARKER_KEY = 'AmIAKeyring?';
let TYPE_MARKER_VALUE = 'Yes!';

function find_dump_raw_keyrings(a: any) {
  if (Array.isArray(a)) {
    for (let i = 0; i < a.length; i++) {
      if (a[i] instanceof AbstractKeyring) {
        a[i] = dump_raw_keyring(a[i]);
      } else {
        find_dump_raw_keyrings(a[i]);
      }
    }
  } else if (a.constructor == Object) {
    for (let k in a) {
      if (a[k] instanceof AbstractKeyring) {
        a[k] = dump_raw_keyring(a[k]);
      } else {
        find_dump_raw_keyrings(a[k]);
      }
    }
  }
}

export function dump_raw_keyring(a: AbstractKeyring): Object {
  let kr;

  if (a instanceof AbstractPrivateKeyring) {
    kr = { enc: a.enc_key, auth: a.auth_key };
  } else if (a instanceof AbstractRoomKeyring) {
    kr = { id: new TextDecoder().decode(a.id), key: a.get_key(AbstractRoomKeyring.PINNED) };
  } else if (a instanceof AbstractPeerAccountKeyring) {
    kr = {
      id: new TextDecoder().decode(a.id),
      pub: {},
      t: a.trusted,
      tofu: a.tofu_fingerprints,
    };
    kr.pub[AbstractSelfAccountKeyring.CURVE25519] = a.get_public_key(
      AbstractSelfAccountKeyring.CURVE25519
    );
    kr.pub[AbstractSelfAccountKeyring.ED25519] = a.get_public_key(
      AbstractSelfAccountKeyring.ED25519
    );
  } else if (a instanceof AbstractSelfAccountKeyring) {
    kr = {
      id: new TextDecoder().decode(a.id),
      pub: {},
      sec: {},
      t: a.trusted,
    };

    kr.pub[AbstractSelfAccountKeyring.CURVE25519] = a.get_public_key(
      AbstractSelfAccountKeyring.CURVE25519
    );
    kr.pub[AbstractSelfAccountKeyring.ED25519] = a.get_public_key(
      AbstractSelfAccountKeyring.ED25519
    );

    kr.sec[AbstractSelfAccountKeyring.MASTER] = a.get_secret_key(AbstractSelfAccountKeyring.MASTER);
    kr.sec[AbstractSelfAccountKeyring.CURVE25519] = a.get_secret_key(
      AbstractSelfAccountKeyring.CURVE25519
    );
    kr.sec[AbstractSelfAccountKeyring.ED25519] = new Uint8Array(
      a.get_secret_key(AbstractSelfAccountKeyring.ED25519)
    ); // remove the 64 byte part, use only the seed for test or storage
  } else {
    throw 'invalid raw keyring input for dump';
  }

  kr[TYPE_MARKER_KEY] = TYPE_MARKER_VALUE;

  return kr;
}

/**
 *
 * THIS FILE IS TESTING THE BOTTOM/NATIVE LAYER OF OUR CRYPTO FUNCIONALITY
 * WE MADE A HANDY INTERFACE FOR THIS TO USE: global-crypto.ts
 *
 */

describe('Cryptography context test', () => {
  let cookie;
  let conf: any = {};

  let options = {
    headers: new HttpHeaders({ 'Content-type': 'application/msgpack' }),
    responseType: 'arraybuffer' as 'arraybuffer',
  };

  let http: HttpClient;
  let apiUrl = environment.url_api + '/';

  let expectedConf = {
    account: ACCOUNT_CRYPTO_CONTEXT.describe(),
    account_room_key: ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT.describe(),
    account_root_key: ACCOUNT_ROOT_KEY_CRYPTO_CONTEXT.describe(),
    account_signature: ACCOUNT_SIGNATURE_CRYPTO_CONTEXT.describe(),
    account_trust: ACCOUNT_TRUST_CRYPTO_CONTEXT.describe(),
    dialogue: DIALOGUE_CRYPTO_CONTEXT.describe(),
    local_storage: LOCAL_STORAGE_CRYPTO_CONTEXT.describe(),
    login_challenge: LOGIN_CHALLENGE_CRYPTO_CONTEXT.describe(),
    nano: NANO_CRYPTO_CONTEXT.describe(),
    nano_data: NANO_DATA_CRYPTO_CONTEXT.describe(),
    nano_own: NANO_OWN_CRYPTO_CONTEXT.describe(),
    private_key: PRIVATE_KEY_CRYPTO_CONTEXT.describe(),
    register_challenge: REGISTER_CHALLENGE_CRYPTO_CONTEXT.describe(),
    room: ROOM_CRYPTO_CONTEXT.describe(),
    room_key: ROOM_KEY_CRYPTO_CONTEXT.describe(),
  };

  let csrfToken = '';

  beforeAll(async () => {
    TestBed.configureTestingModule({
      imports: [HttpClientModule, CookieModule.forRoot()],
      providers: [CookieService],
    });

    http = TestBed.inject(HttpClient);
    cookie = TestBed.inject(CookieService);

    let res = await http.get(apiUrl + 'debug/crypto/context/configs', options).toPromise();
    conf = msgpack.decode(new Uint8Array(res));

    await http.get(apiUrl + 'r/csrf', options).toPromise();
    csrfToken = cookie.get('__Secure-csrf');
    options = {
      headers: new HttpHeaders({
        'Content-type': 'application/msgpack',
        pcsrf: csrfToken,
      }),
      responseType: 'arraybuffer' as 'arraybuffer',
    };
  });

  // check if we can get the conf list from server
  it('should get server conf', () => {
    console.log(conf);
    expect(conf).toBeTruthy();
  });

  function keys_match(a, b) {
    let sa = new Set();
    let sb = new Set();
    for (let key in a) sa.add(key);
    for (let key in b) sb.add(key);
    return sa.size === sb.size && [...sa].every((x) => sb.has(x));
  }

  const create_peer_keyring = (id): PeerAccountKeyring => {
    const kr = SelfAccountKeyring.generate(id);
    return new PeerAccountKeyring(kr.id, kr._public_keys);
  };

  const post_context = async (url_path: string, name: string, version: number, args) => {
    find_dump_raw_keyrings(args);
    let res = await http
      .post(
        apiUrl + url_path,
        new Uint8Array(msgpack.encode({ name: name, version: version, args: args })).buffer,
        options
      )
      .toPromise();
    const data = msgpack.decode(new Uint8Array(res));
    if (data.error) {
      throw new Error(data.error);
    }
    return data;
  };
  const post_context_dump = async (name: string, version: number, args) => {
    const res = await post_context('debug/crypto/context/dump', name, version, args);
    return res.data;
  };
  const post_context_load = async (name: string, version: number, args) => {
    const res = await post_context('debug/crypto/context/load', name, version, args);
    return res.plain;
  };

  const cross_dump_load_context_version = async <
    LoadType extends AbstractConfigLoadArgs,
    DumpType extends AbstractConfigDumpArgs
  >(
    context: CryptoContext<LoadType, DumpType>,
    context_name: string,
    config_version: number,
    plain: Uint8Array,
    server_dump_args: any[],
    server_load_args: any[],
    dump_args_fn: (x: Uint8Array) => DumpType,
    load_args_fn: (x: Uint8Array) => LoadType,
    dump_deterministic: boolean = true,
    prefix_server_dump_with_plain: boolean = true
  ) => {
    const config = context.get_config(config_version);
    expect(config).toBeTruthy();

    let server_dump = await post_context_dump(
      context_name,
      config_version,
      prefix_server_dump_with_plain ? [plain, ...server_dump_args] : server_dump_args
    );
    expect(startswithByteArray(config.version, server_dump)).toBeTrue();

    let server_load = await post_context_load(context_name, config_version, [
      server_dump,
      ...server_load_args,
    ]);
    expect(base64.stringify(plain)).toEqual(base64.stringify(server_load));

    let client_dump = await config.dump(dump_args_fn(plain));
    expect(startswithByteArray(config.version, client_dump)).toBeTrue();

    let client_load = await config.load(load_args_fn(client_dump));
    expect(base64.stringify(plain)).toEqual(base64.stringify(client_load));

    let server_load_client_dump = await post_context_load(context_name, config_version, [
      client_dump,
      ...server_load_args,
    ]);
    expect(base64.stringify(plain)).toEqual(base64.stringify(server_load_client_dump));

    let client_load_server_dump = await config.load(load_args_fn(server_dump));
    expect(base64.stringify(plain)).toEqual(base64.stringify(client_load_server_dump));

    if (dump_deterministic) {
      expect(base64.stringify(server_dump)).toEqual(base64.stringify(client_dump));
    } else {
      expect(base64.stringify(server_dump)).not.toEqual(base64.stringify(client_dump));
    }

    // legacy loading test
    let legacy = false;
    const lvs = context.legacy_versions();
    for (let i = 0; i < lvs.length; i++) {
      if (base64.stringify(lvs[i]) === base64.stringify(config.version)) {
        legacy = true;
        break;
      }
    }
    if (legacy) {
      let legacy_client_load_server_dump = await context.load(
        load_args_fn(server_dump.slice(config.version.length))
      );
      expect(base64.stringify(plain)).toEqual(base64.stringify(legacy_client_load_server_dump));
    }

    // some negative tests for loading
    client_dump[client_dump.length - 2] ^= 64;
    await expectAsync(config.load(load_args_fn(client_dump))).toBeRejected();
    server_dump[server_dump.length - 2] ^= 64;
    await expectAsync(config.load(load_args_fn(server_dump))).toBeRejected();
  };

  // if this arr is changed, we should write more tests for the new elements
  it('check the conf values', (done) => {
    expect(keys_match(conf, expectedConf)).toBeTrue();

    for (let context_name in expectedConf) {
      expect(keys_match(conf[context_name], expectedConf[context_name])).toBeTrue();
      expect(
        conf[context_name]['configs'].length == expectedConf[context_name]['configs'].length
      ).toBeTrue();
      expect(
        conf[context_name]['legacy_versions'].length ==
          expectedConf[context_name]['legacy_versions'].length
      ).toBeTrue();

      for (let i = 0; i < expectedConf[context_name]['configs'].length; i++) {
        expect(
          conf[context_name]['configs'][i].version ==
            expectedConf[context_name]['configs'][i].version &&
            conf[context_name]['configs'][i].never_dump ==
              expectedConf[context_name]['configs'][i].never_dump
        ).toBeTrue();
      }

      for (let i = 0; i < expectedConf[context_name]['legacy_versions'].length; i++) {
        expect(
          base64.stringify(conf[context_name]['legacy_versions'][i]) ==
            base64.stringify(expectedConf[context_name]['legacy_versions'][i])
        ).toBeTrue();
      }
    }

    done();
  });

  it('master derive key', (done) => {
    AccountMasterDeriveKeyV1.derive_key(
      new SelfAccountKeyring('Asdf', [], {
        3: new Uint8Array([
          103, 159, 7, 65, 91, 224, 211, 136, 200, 174, 216, 54, 34, 171, 217, 188, 10, 219, 162,
          103, 132, 161, 150, 36, 138, 175, 27, 253, 80, 149, 212, 222,
        ]),
      }),
      new Uint8Array([120, 121])
    ).then((res) => {
      expect(
        base64.stringify(res) ==
          base64.stringify(
            new Uint8Array([
              199, 153, 224, 11, 134, 19, 93, 1, 253, 191, 83, 170, 105, 114, 44, 103, 158, 213,
              186, 65, 154, 178, 26, 99, 245, 37, 224, 164, 105, 217, 2, 166, 97, 186, 221, 194,
              212, 15, 129, 213, 58, 203, 22, 15, 171, 10, 141, 210, 137, 135, 184, 93, 218, 145,
              217, 33, 245, 3, 71, 84, 218, 46, 169, 144,
            ])
          )
      ).toBeTrue();
      done();
    });
  });

  it('account - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(512));
    let self_kr = SelfAccountKeyring.generate('id');

    await cross_dump_load_context_version(
      ACCOUNT_CRYPTO_CONTEXT,
      'account',
      1,
      plain,
      [self_kr],
      [self_kr],
      (x: Uint8Array) => new AccountConfigDumpArgs(x, self_kr),
      (x: Uint8Array) => new AccountConfigLoadArgs(x, self_kr),
      false
    );
  });

  it('account_room_key - context', async () => {
    let self_kr = SelfAccountKeyring.generate('id');
    let key_version = '1';
    let room_kr = RoomKeyring.generate('id');

    await cross_dump_load_context_version(
      ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT,
      'account_room_key',
      1,
      room_kr.get_key(key_version),
      [self_kr, key_version, room_kr],
      [self_kr, key_version, room_kr],
      (x: Uint8Array) => new AccountRoomKeyConfigDumpArgs(self_kr, key_version, room_kr),
      (x: Uint8Array) => new AccountRoomKeyConfigLoadArgs(x, self_kr, key_version, room_kr),
      true,
      false // NOTE: plain is extracted from the room_kr by the config
    );
  });

  it('account_root_key - context', async () => {
    let self_kr = SelfAccountKeyring.generate('id');
    let key_version = '1';

    await cross_dump_load_context_version(
      ACCOUNT_ROOT_KEY_CRYPTO_CONTEXT,
      'account_root_key',
      1,
      self_kr.get_secret_key(key_version),
      [self_kr, key_version],
      [self_kr, key_version],
      (x: Uint8Array) => new AccountRootKeyConfigDumpArgs(self_kr, key_version),
      (x: Uint8Array) => new AccountRootKeyConfigLoadArgs(x, self_kr, key_version),
      true,
      false // NOTE: plain is extracted from the self_kr by the config
    );
  });

  it('account_signature - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(32));
    let self_kr = SelfAccountKeyring.generate('id');

    await cross_dump_load_context_version(
      ACCOUNT_SIGNATURE_CRYPTO_CONTEXT,
      'account_signature',
      1,
      plain,
      [self_kr],
      [self_kr, plain],
      (x: Uint8Array) => new AccountSignatureConfigDumpArgs(x, self_kr),
      (x: Uint8Array) => new AccountSignatureConfigLoadArgs(x, self_kr, plain)
    );
  });

  it('account_trust - context', async () => {
    let self_kr = SelfAccountKeyring.generate('id');
    let peer_kr = create_peer_keyring('peer');
    let key_version = AbstractSelfAccountKeyring.ED25519;

    await cross_dump_load_context_version(
      ACCOUNT_TRUST_CRYPTO_CONTEXT,
      'account_trust',
      2,
      peer_kr.get_public_key(key_version),
      [self_kr, peer_kr, key_version],
      [self_kr, peer_kr],
      (x: Uint8Array) => new AccountTrustConfigDumpArgs(self_kr, peer_kr, key_version),
      (x: Uint8Array) => new AccountTrustConfigLoadArgs(x, self_kr, peer_kr),
      true,
      false // NOTE: plain is extracted from the peer_kr by the config
    );
  });

  it('timestamp', (done) => {
    try {
      new TimestampWindowCalculator(10, 8);
      expect(false).toBeTrue();
    } catch (e) {
      expect(true).toBeTrue();
    }

    let twc = new TimestampWindowCalculator(10, 2);

    let enc = new TextEncoder();

    [
      [1618658928, enc.encode('1618658920'), [enc.encode('1618658920')]],
      [1618658929, enc.encode('1618658920'), [enc.encode('1618658920'), enc.encode('1618658930')]],
      [1618658930, enc.encode('1618658930'), [enc.encode('1618658930'), enc.encode('1618658920')]],
      [1618658931, enc.encode('1618658930'), [enc.encode('1618658930'), enc.encode('1618658920')]],
      [1618658932, enc.encode('1618658930'), [enc.encode('1618658930')]],
      [1618658933, enc.encode('1618658930'), [enc.encode('1618658930')]],
      [1618658934, enc.encode('1618658930'), [enc.encode('1618658930')]],
      [1618658935, enc.encode('1618658930'), [enc.encode('1618658930')]],
      [1618658936, enc.encode('1618658930'), [enc.encode('1618658930')]],
      [1618658937, enc.encode('1618658930'), [enc.encode('1618658930')]],
      [1618658938, enc.encode('1618658930'), [enc.encode('1618658930')]],
      [1618658939, enc.encode('1618658930'), [enc.encode('1618658930'), enc.encode('1618658940')]],
      [1618658940, enc.encode('1618658940'), [enc.encode('1618658940'), enc.encode('1618658930')]],
      [1618658941, enc.encode('1618658940'), [enc.encode('1618658940'), enc.encode('1618658930')]],
      [1618658942, enc.encode('1618658940'), [enc.encode('1618658940')]],
      [1618658943, enc.encode('1618658940'), [enc.encode('1618658940')]],
      [1618658944, enc.encode('1618658940'), [enc.encode('1618658940')]],
      [1618658945, enc.encode('1618658940'), [enc.encode('1618658940')]],
      [1618658946, enc.encode('1618658940'), [enc.encode('1618658940')]],
      [1618658947, enc.encode('1618658940'), [enc.encode('1618658940')]],
      [1618658948, enc.encode('1618658940'), [enc.encode('1618658940')]],
      [1618658949, enc.encode('1618658940'), [enc.encode('1618658940'), enc.encode('1618658950')]],
      [1618658950, enc.encode('1618658950'), [enc.encode('1618658950'), enc.encode('1618658940')]],
      [1618658951, enc.encode('1618658950'), [enc.encode('1618658950'), enc.encode('1618658940')]],
      [1618658952, enc.encode('1618658950'), [enc.encode('1618658950')]],
    ].forEach((el) => {
      expect(
        base64.stringify(twc.window(<number>el[0])) == base64.stringify(<Uint8Array>el[1])
      ).toBeTrue();

      let arr = twc.windows(<number>el[0]);

      expect(arr.length == (<Uint8Array[]>el[2]).length).toBeTrue();

      for (let i = 0; i < arr.length; i++) {
        expect(base64.stringify(arr[i]) == base64.stringify(<Uint8Array>el[2][i])).toBeTrue();
      }
    });

    expect(true).toBeTrue();
    done();
  });

  it('dialog - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(512));
    let self_kr = SelfAccountKeyring.generate('id');
    let peer_kr = create_peer_keyring('peer');
    let timestamp = 1000;

    await cross_dump_load_context_version(
      DIALOGUE_CRYPTO_CONTEXT,
      'dialogue',
      1,
      plain,
      [self_kr, peer_kr, timestamp],
      [self_kr, peer_kr, timestamp],
      (x: Uint8Array) => new DialogueConfigDumpArgs(x, self_kr, peer_kr, timestamp),
      (x: Uint8Array) => new DialogueConfigLoadArgs(x, self_kr, peer_kr, timestamp),
      false
    );
  });

  it('local_storage - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(128));
    let server_key = window.crypto.getRandomValues(new Uint8Array(16));
    let client_key = window.crypto.getRandomValues(new Uint8Array(32));
    let salt = window.crypto.getRandomValues(new Uint8Array(16));

    await cross_dump_load_context_version(
      LOCAL_STORAGE_CRYPTO_CONTEXT,
      'local_storage',
      1,
      plain,
      [server_key, client_key],
      [server_key, client_key],
      (x: Uint8Array) => new LocalStorageConfigDumpArgs(x, server_key, client_key),
      (x: Uint8Array) => new LocalStorageConfigLoadArgs(x, server_key, client_key),
      false
    );

    await cross_dump_load_context_version(
      LOCAL_STORAGE_CRYPTO_CONTEXT,
      'local_storage',
      2,
      plain,
      [server_key, client_key, salt],
      [server_key, client_key, salt],
      (x: Uint8Array) => new LocalStorageConfigDumpArgs(x, server_key, client_key, salt),
      (x: Uint8Array) => new LocalStorageConfigLoadArgs(x, server_key, client_key, salt),
      false
    );
  });

  it('login_challenge - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(128));
    let self_kr = SelfAccountKeyring.generate('id');
    let peer_kr = create_peer_keyring('peer');

    await cross_dump_load_context_version(
      LOGIN_CHALLENGE_CRYPTO_CONTEXT,
      'login_challenge',
      1,
      plain,
      [self_kr, peer_kr],
      [self_kr, peer_kr],
      (x: Uint8Array) => new LoginChallengeConfigDumpArgs(x, self_kr, peer_kr),
      (x: Uint8Array) => new LoginChallengeConfigLoadArgs(x, self_kr, peer_kr),
      false
    );
  });

  it('nano - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(512));
    let self_kr = SelfAccountKeyring.generate('id');
    let peer_kr = create_peer_keyring('peer');
    let room_kr = RoomKeyring.generate('room');
    let timestamp = 1000;

    await cross_dump_load_context_version(
      NANO_CRYPTO_CONTEXT,
      'nano',
      1,
      plain,
      [self_kr, peer_kr, room_kr, timestamp],
      [self_kr, peer_kr, room_kr, timestamp],
      (x: Uint8Array) => new NanoConfigDumpArgs(x, self_kr, peer_kr, room_kr, timestamp),
      (x: Uint8Array) => new NanoConfigLoadArgs(x, self_kr, peer_kr, room_kr, timestamp),
      false
    );
  });

  it('nano_data - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(512));
    let key = window.crypto.getRandomValues(new Uint8Array(32));

    await cross_dump_load_context_version(
      NANO_DATA_CRYPTO_CONTEXT,
      'nano_data',
      1,
      plain,
      [key],
      [key],
      (x: Uint8Array) => new NanoDataConfigDumpArgs(x, key),
      (x: Uint8Array) => new NanoDataConfigLoadArgs(x, key),
      false
    );
  });

  it('nano_own - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(512));
    let self_kr = SelfAccountKeyring.generate('id');
    let timestamp = 1000;

    await cross_dump_load_context_version(
      NANO_OWN_CRYPTO_CONTEXT,
      'nano_own',
      1,
      plain,
      [self_kr, timestamp],
      [self_kr, timestamp],
      (x: Uint8Array) => new NanoOwnConfigDumpArgs(x, self_kr, timestamp),
      (x: Uint8Array) => new NanoOwnConfigLoadArgs(x, self_kr, timestamp),
      false
    );
  });

  it('private_key - context', async () => {
    let self_kr = SelfAccountKeyring.generate('id');
    let private_kr = await PrivateKeyringV1.load(
      new TextEncoder().encode('password'),
      window.crypto.getRandomValues(new Uint8Array(32))
    );
    let key_version = AbstractSelfAccountKeyring.MASTER;

    await cross_dump_load_context_version(
      PRIVATE_KEY_CRYPTO_CONTEXT,
      'private_key',
      1,
      self_kr.get_secret_key(key_version),
      [self_kr, private_kr, key_version],
      [private_kr, key_version],
      (x: Uint8Array) => new PrivateKeyConfigDumpArgs(self_kr, private_kr, key_version),
      (x: Uint8Array) => new PrivateKeyConfigLoadArgs(x, private_kr, key_version),
      true,
      false // NOTE: plain is extracted from the self_kr by the config
    );
  });

  it('register_challenge - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(128));
    let self_kr = SelfAccountKeyring.generate('id');
    let peer_kr = create_peer_keyring('peer');

    await cross_dump_load_context_version(
      REGISTER_CHALLENGE_CRYPTO_CONTEXT,
      'register_challenge',
      1,
      plain,
      [self_kr, peer_kr],
      [self_kr, peer_kr],
      (x: Uint8Array) => new RegisterChallengeConfigDumpArgs(x, self_kr, peer_kr),
      (x: Uint8Array) => new RegisterChallengeConfigLoadArgs(x, self_kr, peer_kr),
      false
    );
  });

  it('room - context', async () => {
    let plain = window.crypto.getRandomValues(new Uint8Array(512));
    let room_kr = RoomKeyring.generate('room');
    let timestamp = 1000;

    await cross_dump_load_context_version(
      ROOM_CRYPTO_CONTEXT,
      'room',
      1,
      plain,
      [room_kr, timestamp],
      [room_kr, timestamp],
      (x: Uint8Array) => new RoomConfigDumpArgs(x, room_kr, timestamp),
      (x: Uint8Array) => new RoomConfigLoadArgs(x, room_kr, timestamp),
      false
    );
  });

  it('room_key - context', async () => {
    let self_kr = SelfAccountKeyring.generate('id');
    let peer_kr = create_peer_keyring('peer');
    let room_kr = RoomKeyring.generate('room');
    let key_version = RoomKeyring.PINNED;

    await cross_dump_load_context_version(
      ROOM_KEY_CRYPTO_CONTEXT,
      'room_key',
      1,
      room_kr.get_key(key_version),
      [self_kr, peer_kr, room_kr, key_version],
      [self_kr, peer_kr, room_kr, key_version],
      (x: Uint8Array) => new RoomKeyConfigDumpArgs(self_kr, peer_kr, room_kr, key_version),
      (x: Uint8Array) => new RoomKeyConfigLoadArgs(x, self_kr, peer_kr, room_kr, key_version),
      true,
      false // NOTE: plain is extracted from the room_kr by the config
    );
  });

  it('local storage dump - load', (done) => {
    let self_kr = SelfAccountKeyring.generate('accId');

    let server_key = window.crypto.getRandomValues(new Uint8Array(16));
    let client_key = window.crypto.getRandomValues(new Uint8Array(32));
    let salt = window.crypto.getRandomValues(new Uint8Array(16));

    self_kr.dump_local_storage(server_key, client_key, salt).then((skr) => {
      SelfAccountKeyring.load_local_storage(skr, server_key, client_key, salt).then((self_kr3) => {
        expect(base64.stringify(self_kr.id) == base64.stringify(self_kr3.id)).toBeTrue();
        expect(
          base64.stringify(self_kr._public_keys[SelfAccountKeyring.CURVE25519]) ==
            base64.stringify(self_kr3._public_keys[SelfAccountKeyring.CURVE25519])
        ).toBeTrue();
        expect(
          base64.stringify(self_kr._public_keys[SelfAccountKeyring.ED25519]) ==
            base64.stringify(self_kr3._public_keys[SelfAccountKeyring.ED25519])
        ).toBeTrue();
        expect(
          base64.stringify(self_kr._secret_keys[SelfAccountKeyring.CURVE25519]) ==
            base64.stringify(self_kr3._secret_keys[SelfAccountKeyring.CURVE25519])
        ).toBeTrue();
        expect(
          base64.stringify(self_kr._secret_keys[SelfAccountKeyring.ED25519]) ==
            base64.stringify(self_kr3._secret_keys[SelfAccountKeyring.ED25519])
        ).toBeTrue();
        expect(
          base64.stringify(self_kr._secret_keys[SelfAccountKeyring.MASTER]) ==
            base64.stringify(self_kr3._secret_keys[SelfAccountKeyring.MASTER])
        ).toBeTrue();
        done();
      });
    });
  });
});
