import { AbstractAccountKeyring, AbstractSelfAccountKeyring } from './account_base';
import { AbstractRoomKeyring } from './room_base';
import { ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT } from '../context/account_key/room/__init__';
import {
  AccountRoomKeyConfigDumpArgs,
  AccountRoomKeyConfigLoadArgs,
} from '../context/account_key/room/args';
import { ROOM_KEY_CRYPTO_CONTEXT } from '../context/room_key/__init__';
import { RoomKeyConfigDumpArgs, RoomKeyConfigLoadArgs } from '../context/room_key/args';
import { RandomKeyGenerator256 } from '../base/key/random';
import { MissingKey } from './base';
import { base64 } from 'rfc4648';

/**
 * This is a separate module to resolve a cyclic import.
 */
export class RoomKeyring extends AbstractRoomKeyring {
  public static load(
    self_kr: AbstractSelfAccountKeyring,
    peer_kr: AbstractAccountKeyring,
    room_id: string,
    saved_key?: Uint8Array,
    pinned_key?: Uint8Array
  ): Promise<RoomKeyring> {
    let p: Promise<any>; // sync crypto load

    if (!saved_key && !pinned_key) {
      p = Promise.reject(new MissingKey('MissingKey Pinned key is missing'));
    }
    let room_kr = new RoomKeyring(room_id, {});

    let keys = {};

    if (saved_key) {
      p = ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT.load(
        new AccountRoomKeyConfigLoadArgs(saved_key, self_kr, RoomKeyring.PINNED, room_kr)
      ).then((k) => {
        keys[RoomKeyring.PINNED] = k;
        room_kr.update(keys);
        return room_kr;
      });
    } else {
      p = Promise.resolve();
    }

    return p.then(() => {
      let p2: Promise<Uint8Array>;

      if (base64.stringify(self_kr['id']) == base64.stringify(peer_kr['id'])) {
        if (!keys.hasOwnProperty(RoomKeyring.PINNED)) {
          p2 = ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT.load(
            new AccountRoomKeyConfigLoadArgs(pinned_key, self_kr, RoomKeyring.PINNED, room_kr)
          );
        }
      } else {
        if (!keys.hasOwnProperty(RoomKeyring.PINNED)) {
          p2 = ROOM_KEY_CRYPTO_CONTEXT.load(
            new RoomKeyConfigLoadArgs(pinned_key, self_kr, peer_kr, room_kr, RoomKeyring.PINNED)
          );
        }
      }

      if (!p2) return Promise.resolve(room_kr);

      return p2.then((k) => {
        keys[RoomKeyring.PINNED] = k;
        room_kr.update(keys);
        return room_kr;
      });
    });
  }

  public static loadRaw(room_id: string, pinned_key: Uint8Array): Promise<RoomKeyring> {
    return Promise.resolve(
      new RoomKeyring(room_id, {
        [RoomKeyring.PINNED]: pinned_key,
      })
    );
  }

  public dump(
    self_kr: AbstractSelfAccountKeyring,
    peer_kr: AbstractAccountKeyring
  ): Promise<Object> {
    let keys = {};
    if (base64.stringify(self_kr['id']) == base64.stringify(peer_kr['id'])) {
      // TODO test this
      return ACCOUNT_ROOM_KEY_CRYPTO_CONTEXT.dump(
        new AccountRoomKeyConfigDumpArgs(self_kr, RoomKeyring.PINNED, this)
      ).then((k) => {
        keys[RoomKeyring.PINNED] = k;
        return keys;
      });
    } else {
      return ROOM_KEY_CRYPTO_CONTEXT.dump(
        new RoomKeyConfigDumpArgs(self_kr, peer_kr, this, RoomKeyring.PINNED)
      ).then((k) => {
        keys[RoomKeyring.PINNED] = k;
        return keys;
      });
    }
  }

  public static generate(room_id: string) {
    return new RoomKeyring(room_id, {
      [RoomKeyring.PINNED]: RandomKeyGenerator256.new_key(),
    });
  }
}
