import { Injectable } from '@angular/core';
import {
  createNewIdQuery,
  createRoomConfigBlockQuery,
  createRoomQuery,
  deleteRoomQuery,
  editRoomAccountPermissionQuery,
  editRoomDataQuery,
  editRoomPermissionQuery,
  getAllRoomQuery,
  getRoomDatasQuery,
  getRoomDetailQuery,
  getRoomQuery,
  getRoomWithDataQuery,
  joinRoomQuery,
  leaveRoomQuery,
  roomConfigQuery,
  roomMemberQuery,
  roomMyPermissionsQuery,
  roomPermissionsQuery,
} from '../server-services/querys';
import { ServerRestApiService } from '../services/server-rest-api.service';
import { AccountService } from './account.service';
import { AuthService } from './auth.service';
import { RoomKeyring } from '../crypto/keyring/room';
import { GroupConfigBlockChain } from '../crypto/group_config/chain';
import { GroupConfigBlockFlags, GroupConfigBlockMemberFlags } from '../crypto/group_config/block';
import { marker } from '@biesbjerg/ngx-translate-extract-marker';
import { FlagParser, RoomPermissions } from '../crypto/group_config/chain-flag-parser';
import {
  RoomRecord,
  roomConfigRecord,
  RoomPermissionRecord,
  RoomMyPermissionRecord,
  RoomMemberRecord,
  RoomWithData,
  RawRoomWithData,
  RoomData,
  ERROR_NOT_AVAILABLE,
  ERROR_DATASET_ERROR,
  ERROR_DECRYPTION_ERROR,
  RoomMember,
  RoomDetail,
} from './query-records/room-records';
import { RoomCrypto } from '../crypto/top/room';
import { chunk } from 'lodash';
import { CacheService } from '../services/cache/cache.service';
import { DecryptionState, ID } from './query-records/common-records';
import { base64 } from 'rfc4648';
import { environment } from 'src/environments/environment';
import { RouterHandler } from '../services/router-handler.service';
import { AbstractRoomKeyring } from '../crypto/keyring/room_base';

export enum RoomPermissionAttributes {
  MESSAGE_BOARD_READ_ONLY = 'msgBrdReadOnly',
  NO_REACTIONS = 'noReactions',
}

@Injectable({
  providedIn: 'root',
})
export class RoomService {
  /**
   * Cache for keyrings
   */
  private roomKeyrings = {};

  constructor(
    private serverRestApiService: ServerRestApiService,
    private accountService: AccountService,
    private authService: AuthService,
    private cacheService: CacheService,
    private routerHandler: RouterHandler
  ) {
    authService.onLogout(() => {
      this.roomKeyrings = {};
    });
  }

  public getKeyring(resourceId): Promise<RoomKeyring> {
    if (this.roomKeyrings.hasOwnProperty(resourceId)) {
      if (!this.roomKeyrings[resourceId]) {
        return Promise.reject('Could not generate keyring for this room: ' + resourceId);
      } else {
        return Promise.resolve(this.roomKeyrings[resourceId]);
      }
    }
    return this.getRoom(resourceId).then((room) => {
      return this.makeKeyring(room);
    });
  }

  public makeKeyring(roomData: RoomRecord): Promise<RoomKeyring> {
    if (!this.roomKeyrings.hasOwnProperty(roomData.ownerAccountId)) {
      if (!roomData.pinnedKey) {
        if (this.pinnedKeys[roomData.id]) {
          return RoomKeyring.loadRaw(roomData.id, this.pinnedKeys[roomData.id]).then((room_kr) => {
            this.roomKeyrings[roomData.id] = room_kr;
            return room_kr;
          });
        } else {
          console.error('did not get pinned key', roomData.pinnedKey);
          return Promise.reject('need pinned key for operation');
        }
      } else {
        return this.accountService.getPeerKeyring(roomData.ownerAccountId).then((peer_kr) => {
          return RoomKeyring.load(
            this.authService.getSelfAccountKeyring(),
            peer_kr,
            roomData.id,
            roomData.savedKey,
            roomData.pinnedKey
          )
            .then((room_kr) => {
              // save keyring for future request
              this.roomKeyrings[roomData.id] = room_kr;

              return room_kr;
            })
            .catch((err) => {
              this.roomKeyrings[roomData.id] = null;
              return Promise.reject('can not load roomkeyring');
            });
        });
      }
    } else {
      if (this.roomKeyrings[roomData.ownerAccountId]) {
        return Promise.resolve(this.getKeyring(roomData.id));
      } else {
        return Promise.reject('can not load roomkeyring');
      }
    }
  }

  /**
   * Note: If you get data from workspace you will get the data from the cache. So you
   * have a good chance, this decrypter wont get called.
   */
  public decryptRoomData(roomRecord: RoomRecord, encryptedRoomData: Uint8Array): Promise<RoomData> {
    let data: RoomData = {
      decryptionError: true,
      decryptionErrorMessage: marker('Decryption initialization skipped'),
    };

    if (
      roomRecord.inaccessible === true ||
      (!roomRecord.savedKey && !roomRecord.pinnedKey && !this.pinnedKeys[roomRecord.id])
    ) {
      data.decryptionErrorMessage = ERROR_NOT_AVAILABLE;
      data.rawData = encryptedRoomData;

      return Promise.resolve(data);
    } else {
      if (!encryptedRoomData) {
        data.decryptionErrorMessage = ERROR_DATASET_ERROR;
        return Promise.resolve(data);
      } else {
        return this.makeKeyring(roomRecord)
          .then((room_kr) => {
            return RoomCrypto.decrypt(encryptedRoomData, roomRecord.id, room_kr);
          })
          .then((loadedData: RoomData /* Without DecryptionState */) => {
            if (typeof loadedData === 'object' && loadedData !== null) {
              data = loadedData;
              data.decryptionError = false;
              return data;
            } else {
              data.decryptionErrorMessage = ERROR_DATASET_ERROR;
              return data;
            }
          })
          .catch((err) => {
            data.decryptionErrorMessage = ERROR_DECRYPTION_ERROR;
            return data;
          });
      }
    }
  }

  private encryptData(data, extra): Promise<Object> {
    return this.getRoomRecord(data['roomId'])
      .then((roomData) => {
        return this.makeKeyring(roomData);
      })
      .then((room_kr) => {
        return RoomCrypto.encrypt(data['data'], data['roomId'], room_kr);
      })
      .then((dump) => {
        data['data'] = dump;
        return data;
      });
  }

  private pinnedKeys: { [roomId: string]: Uint8Array } = {};

  public registerPinnedKey(roomId: ID, pinnedKeyQueryParam: string) {
    try {
      let parsedPinnedKey: Uint8Array = base64.parse(pinnedKeyQueryParam);
      this.pinnedKeys[roomId] = parsedPinnedKey;
    } catch (e) {
      console.error('can not parse pinned key');
      // there is a chance, we do not need the pinned key
      // when we are logged in and we are member of the room
    }
  }

  // direct api-calls --------------------------------------

  /**
   * get The Room
   * @param id
   * @returns
   */
  public getRoom(id): Promise<RoomWithData> {
    let room: RoomWithData | RoomRecord = this.cacheService.getCacheDataByRequest({
      query: getRoomQuery,
      variables: {
        id,
      },
    });
    //console.log('getRoom', id);
    //console.trace();
    if (room) {
      let p: Promise<RoomWithData>;
      if ('data' in room && !room['__refetch']) {
        p = Promise.resolve(<RoomWithData>room);
      } else {
        // this call collect the async calls for a short period of time, and start a batch query
        p = this.loadData(room);
      }

      return p.then((room) => {
        // try to redecrypt the room if we finally have a pinnedKey for that
        if (
          room.data.decryptionError &&
          room.data.decryptionErrorMessage == ERROR_NOT_AVAILABLE &&
          room.data.rawData &&
          this.pinnedKeys[room.id]
        ) {
          // this is an advanced edge-case handling
          // room loaded into the cache because it is in your workspace
          // but unavailable (maybe you've been kicked), so you do not have pinned key
          // but the room is public and you have a fragment pinnedKey
          // the workspace decryption run earlier than the routerHandler init
          // normally it is handled in the resource-page component, but now
          // it is already loaded into the cache

          return this.decryptRoomData(room, room.data.rawData).then((roomData) => {
            console.log('redecrypt room', roomData);
            room.data = roomData;
            delete room.data.rawData;
            return room;
          });
        } else {
          return room;
        }
      });
    } else {
      // we dont have batch query yet for unknown records, so need to query one-by-one
      return this.serverRestApiService
        .query({
          query: getRoomWithDataQuery,
          variables: {
            id,
          },
          decrypt: (data: RawRoomWithData, variables, extra) => {
            return this.decryptRoomData(<RoomRecord>data, data.data).then((roomData) => {
              // recast the data from Uint8Array to RoomData
              let resultRoom = <RoomWithData>(<any>data);
              resultRoom.data = roomData;
              return resultRoom;
            });
          },
        })
        .catch((err) => {
          console.warn('get room err', id, err);
          return Promise.reject({
            message: 'can not get room data from server',
            err,
            decrypt: false,
            fetch: false,
          });
        });
    }
  }

  /**
   * Fastest way to get the room record if not necessary to have the RoomData
   */
  public getRoomRecord(roomId: string): Promise<RoomRecord | RoomWithData> {
    let room: RoomRecord | RoomWithData = this.cacheService.getCacheDataByRequest({
      query: getRoomQuery,
      variables: {
        id: roomId,
      },
    });

    if (room) {
      return Promise.resolve(room);
    } else {
      return this.getRoom(roomId);
    }
  }

  /**
   * Attach the decrypted Data on the passed room reference if it is in the cache
   * @param room
   * @returns
   */
  private loadData(room: RoomRecord): Promise<RoomWithData> {
    return this.loadDatas([room]).then((result) => {
      if (!result[room.id].data) {
        console.warn('There is no data for room', room);
        if (result[room.id].inaccessible) {
          result[room.id].data = {
            decryptionError: true,
            decryptionErrorMessage: ERROR_NOT_AVAILABLE,
          };
        } else {
          result[room.id].data = {
            decryptionError: true,
            decryptionErrorMessage: ERROR_DATASET_ERROR,
          };
        }
      }
      return result[room.id];
    });
  }

  /**
   * Batch query for the room datas. Only available with room records.
   * We can not decrypt room if there is no record for that.
   */
  private loadDatas(rooms: RoomRecord[]): Promise<{ [key: string]: RoomWithData }> {
    let notCachedDataForRoomRecord: RoomRecord[] = [];
    let result: { [key: string]: RoomWithData } = {};

    for (let i = 0; i < rooms.length; i++) {
      let room: RoomRecord | RoomWithData = this.cacheService.getCacheDataByRequest({
        query: getRoomQuery,
        variables: {
          id: rooms[i].id,
        },
      });

      // later the cache will assign the data to the room reference, so we can
      // collect the found and not found reference too
      result[room.id] = <RoomWithData>room;

      if (!('data' in room) || room['__refetch']) {
        // mark for query
        notCachedDataForRoomRecord.push(room);
      }
    }

    if (notCachedDataForRoomRecord.length == 0) {
      // we have found all the record from the cache
      return Promise.resolve(<{ [key: string]: RoomWithData }>result);
    } else {
      // we need to get the not cached roomDatas
      return this.queryRoomDataWindowedFetch(notCachedDataForRoomRecord).then(
        (roomDataFromServer) => {
          // this is only the notCached references
          // cache layer assigned the data to the record reference
          return result; // return with all
        }
      );
    }
  }

  private cachedLoadDataCallbacks: {
    resolve: Function;
    reject: Function;
    rooms: RoomRecord[];
  }[] = [];
  public static LOAD_DATA_WAITING_WINDOW = 50;
  private loadDataStarted: boolean = false;

  private queryRoomDataWindowedFetch(
    rooms: RoomRecord[]
  ): Promise<{ [key: string]: RoomWithData }> {
    return new Promise((resolve, reject) => {
      this.cachedLoadDataCallbacks.push({ resolve, reject, rooms });

      if (!this.loadDataStarted) {
        this.loadDataStarted = true;

        setTimeout(() => {
          let waitingCallbacks = this.cachedLoadDataCallbacks;
          try {
            // let the system collect the next payload
            this.cachedLoadDataCallbacks = [];
            this.loadDataStarted = false;

            let roomForFetch = [];

            // merge rooms
            waitingCallbacks.forEach((waitingObject) => {
              waitingObject.rooms.forEach((currRoom) => {
                if (roomForFetch.indexOf(currRoom) == -1) {
                  roomForFetch.push(currRoom);
                }
              });
            });

            // fetch data for the references
            this.queryRoomData(roomForFetch)
              .then((result) => {
                waitingCallbacks.forEach((callbackObject) => {
                  callbackObject.resolve(callbackObject.rooms); // references got the RoomData
                });
              })
              .catch((e) => {
                console.error('room query error', e);
                waitingCallbacks.forEach((callbackObject) => {
                  callbackObject.reject(e);
                });
              });
          } catch (e) {
            console.error('window fetch error', e);
            waitingCallbacks.forEach((callbackObject) => {
              callbackObject.reject(e);
            });
          }
        }, RoomService.LOAD_DATA_WAITING_WINDOW);
      }
    });
  }

  /**
   * call a query for all of the roomRecord
   */
  private queryRoomData(rooms: RoomRecord[]): Promise<{ [key: string]: RoomData }> {
    let roomMap: { [key: string]: RoomRecord | RoomWithData } = {};
    let roomIds: string[] = [];

    rooms.forEach((oneRoom) => {
      roomMap[oneRoom.id] = oneRoom;
      roomIds.push(oneRoom.id);
    });

    let batchSize: number = 15;
    let idBatches: string[][] = chunk(roomIds, batchSize);
    let batchPromises: Promise<{ [key: string]: RoomData }>[] = [];
    let allRoomDatas: any = {};

    idBatches.forEach((idBatch, i) => {
      let delayMs = i * 200;
      let delayedQueryPromise: Promise<{ [key: string]: RoomData }> = new Promise(
        (resolve, reject) => {
          setTimeout(() => {
            try {
              this.serverRestApiService
                .query({
                  query: getRoomDatasQuery,
                  variables: {
                    ids: idBatch,
                  },
                  decrypt: (data: { [key: string]: Uint8Array }, variables, extra) => {
                    let p: Promise<any>[] = [];
                    let decryptedDatas: { [key: string]: RoomData } = {};
                    for (var id in data) {
                      let roomRecord = roomMap[id];
                      let encryptedData = data[id];
                      p.push(
                        this.decryptRoomData(roomRecord, encryptedData)
                          .then((roomData) => {
                            decryptedDatas[roomRecord.id] = roomData;
                          })
                          .catch((e) => {
                            console.warn('room decrypt error', e);
                          })
                      );
                    }
                    return Promise.all(p).then(() => {
                      return decryptedDatas;
                    });
                  },
                })
                .then((result) => {
                  resolve(result);
                });
            } catch (e) {
              reject(e);
            }
          }, delayMs);
        }
      );

      batchPromises.push(delayedQueryPromise);
    });

    return new Promise((resolve, reject) => {
      Promise.all(batchPromises).then((roomBatches) => {
        roomBatches.forEach((batch) => {
          Object.keys(batch).forEach((key) => {
            allRoomDatas[key] = batch[key];
          });
        });
        resolve(allRoomDatas);
      });
    });
  }

  public getNanoSession(roomId): Promise<{ id: string; version: number } | null> {
    let nanoSessions: { id: string; version: number }[] = [];

    return this.getRoomRecord(roomId).then((roomData) => {
      for (let sessionId in roomData.nanoSessions) {
        nanoSessions.push({
          id: sessionId,
          version: roomData.nanoSessions[sessionId],
        });
      }

      if (nanoSessions.length > 1 || nanoSessions.length == 0) return null;
      else {
        return nanoSessions[0];
      }
    });
  }

  public editData(id, name?: string, avatar?: ArrayBuffer) {
    return this.serverRestApiService.mutate({
      query: editRoomDataQuery,
      encrypt: this.encryptData.bind(this),
      variables: {
        roomId: id,
        data: {
          name,
          avatar,
        },
      },
    });
  }

  public editPermission(id, attribute: RoomPermissionAttributes, value: any) {
    return this.serverRestApiService.mutate({
      query: editRoomPermissionQuery,
      variables: {
        roomId: id,
        attribute,
        value,
      },
    });
  }

  public getAllRoom(): Promise<RoomRecord[]> {
    return this.serverRestApiService.query({
      query: getAllRoomQuery,
    });
  }

  public getAllRoomWithActiveDrive(): Promise<RoomRecord[]> {
    let roomsPromise = new Promise<RoomRecord[]>((resolve) => {
      this.serverRestApiService.query({ query: getAllRoomQuery }).then((rooms) => {
        let roomsWithDrive = rooms.filter(
          (room) => room.nanoSessions && Object.keys(room.nanoSessions).length > 0
        );

        resolve(roomsWithDrive);
      });
    });

    return roomsPromise;
  }

  public getMyRooms(): Promise<RoomRecord[]> {
    return this.getAllRoom().then((rooms) => {
      return this.accountService.getMe().then((me) => {
        let results = [];
        for (let i = 0; i < rooms.length; i++) {
          if (rooms[i].ownerAccountId == me.id) {
            results.push(rooms[i]);
          }
        }
        return results;
      });
    });
  }

  public getRoomBlockChain(resourceId, ownerKr): Promise<GroupConfigBlockChain> {
    return this.serverRestApiService
      .query({
        query: roomConfigQuery,
        variables: { id: resourceId },
      })
      .then((res: roomConfigRecord[]) => {
        let blocks = [];
        console.log('conf res', res);
        res.forEach((element) => {
          blocks.push([element.block, element.signature]);
        });

        return GroupConfigBlockChain.load(resourceId, blocks, ownerKr).then((chain) => {
          return chain;
        });
      });
  }

  public getRoomConfig(resourceId): Promise<FlagParser> {
    return this.getRoomRecord(resourceId).then((roomData) => {
      return this.accountService.getPeerKeyring(roomData.ownerAccountId).then((peerKr) => {
        return this.getRoomBlockChain(resourceId, peerKr).then((chain) => {
          return new FlagParser(chain);
        });

        //chain.new_block()

        // new_block viszsaad: raw_block, signature, member_deltas, group_deltas
        // member_deltas ból kinyerem az id-ket. Csinálok objot:
        // {id: roomkeyring.dump(selfkeyring, peerKeyring(id)).pinnedKey}
        // createRoom(roomId, rawBlock, signature, generált obj)
      });
    });
  }

  public newRoom(name): Promise<string> {
    console.log('Begin create room with name', name);

    return this.serverRestApiService
      .mutate({
        query: createNewIdQuery,
        variables: {
          type: 'room-id',
        },
      })
      .then((id) => {
        console.log('New room id is', id);

        let roomKr = RoomKeyring.generate(id);

        return GroupConfigBlockChain.load(id, [], this.authService.getSelfAccountKeyring()).then(
          (chain) => {
            return this.accountService.getMe().then((me) => {
              return chain
                .new_block(
                  this.authService.getSelfAccountKeyring(),
                  id,
                  {
                    [me.id]: FlagParser.allFlag(),
                  },
                  0
                )
                .then(([newBlock, newBlockSignature, memberDeltas, groupDeltas]) => {
                  let deltaKeys = {};
                  //console.log("memberDeltas", memberDeltas);

                  let promises: Promise<void>[] = [];

                  for (let key in memberDeltas) {
                    // its only me now
                    if ((memberDeltas[key] & GroupConfigBlockMemberFlags.ACTIVE) == 1) {
                      promises.push(
                        this.accountService.getPeerKeyring(key).then((peerKr) => {
                          return roomKr
                            .dump(this.authService.getSelfAccountKeyring(), peerKr)
                            .then((dump) => {
                              deltaKeys[key] = dump[RoomKeyring.PINNED];
                              return;
                            });
                        })
                      );
                    }
                  }

                  return Promise.all(promises).then(() => {
                    return this.serverRestApiService
                      .mutate({
                        query: createRoomQuery,
                        variables: {
                          roomId: id,
                          newBlock,
                          newBlockSignature,
                          deltaKeys,
                        },
                      })
                      .then((res) => {
                        // the room is created, set the name too

                        console.log('Room created', res, id, name);

                        return this.editData(id, name)
                          .then((res) => {
                            console.log('set room data', res);
                            return id;
                          })
                          .catch(() => {
                            // found a case, when the editData failed somewhy, just letry once
                            return this.editData(id, name).then((res) => {
                              console.log('set room data: 2', res);
                              return true;
                            });
                          });
                      });
                  });
                });
            });
          }
        );
      });
  }

  public deleteRoom(resourceId) {
    return this.serverRestApiService.mutate({
      query: deleteRoomQuery,
      variables: {
        roomId: resourceId,
      },
    });
  }

  private getChain(resourceId): Promise<GroupConfigBlockChain> {
    return this.serverRestApiService
      .query({
        query: roomConfigQuery,
        variables: { id: resourceId },
      })
      .then((res: roomConfigRecord[]) => {
        let blocks = [];

        res.forEach((element) => {
          blocks.push([element.block, element.signature]);
        });

        return GroupConfigBlockChain.load(
          resourceId,
          blocks,
          this.authService.getSelfAccountKeyring()
        );
      });
  }

  public saveConfig(
    resourceId,
    members: RoomMember[],
    roomPermissions: RoomPermissions
  ): Promise<any> {
    return this.serverRestApiService
      .mutate({
        query: createNewIdQuery,
      })
      .then((id) => {
        //console.log("newId", id);
        //chain.new_block()

        // new_block viszsaad: raw_block, signature, member_deltas, group_deltas
        // member_deltas ból kinyerem az id-ket. Csinálok objot:
        // {id: roomkeyring.dump(selfkeyring, peerKeyring(id)).pinnedKey}
        // createRoom(roomId, rawBlock, signature, generált obj)
        let permissions = {};
        members.forEach((m) => {
          if (m.hasChanged) {
            permissions[m.user.id] = FlagParser.parseToFlags(m.permissions, m.newPermissions);
          }
        });

        let roomFlag = 0;
        if (roomPermissions.allowAnonym === true) {
          roomFlag = GroupConfigBlockFlags.ALLOW_ANONYMOUS;
        } else if (roomPermissions.allowAnonym === false) {
          roomFlag = GroupConfigBlockFlags.ALLOW_ANONYMOUS_NEGATE;
        }

        if (Object.keys(permissions).length > 0 || Object.keys(roomPermissions).length > 0) {
          return this.getChain(resourceId).then((chain) => {
            return chain
              .new_block(this.authService.getSelfAccountKeyring(), id, permissions, roomFlag)
              .then(async ([newBlock, newBlockSignature, memberDeltas, groupDeltas]) => {
                let deltaKeys = {};

                let roomKr = await this.makeKeyring(await this.getRoomRecord(resourceId));

                for (let key in memberDeltas) {
                  // only active users
                  if ((memberDeltas[key] & GroupConfigBlockMemberFlags.ACTIVE) == 1) {
                    let dump = await roomKr
                      .dump(
                        this.authService.getSelfAccountKeyring(),
                        await this.accountService.getPeerKeyring(key)
                      )
                      .catch((err) => {
                        console.error('can not dump key', err);
                        throw err;
                      });

                    deltaKeys[key] = dump[RoomKeyring.PINNED];
                  }
                }

                //console.log("deltas", Object.assign({}, deltaKeys));
                //return new Promise((resolve, reject)=>{
                //setTimeout(()=>{
                return this.serverRestApiService
                  .mutate({
                    query: createRoomConfigBlockQuery,
                    variables: {
                      roomId: resourceId,
                      newBlock,
                      newBlockSignature,
                      deltaKeys,
                    },
                  })
                  .then((res) => {
                    //console.log("res", res);
                    //resolve(true)
                    return true;
                  }); //.catch(reject)
                //}, 5000);
                //})
              });
          });
        } else {
          //console.log("nothing to change in config block");
          return true;
        }
      });
  }

  public getRoomPermissions(resourceId): Promise<RoomPermissionRecord> {
    return this.serverRestApiService
      .query({
        query: roomPermissionsQuery,
        variables: { id: resourceId },
      })
      .then((res: RoomPermissionRecord) => {
        //console.log("res", res);
        return res;
      });
  }

  public getMyRoomPermissions(resourceId): Promise<RoomMyPermissionRecord> {
    return this.serverRestApiService
      .query({
        query: roomMyPermissionsQuery,
        variables: { id: resourceId },
      })
      .then((res: RoomMyPermissionRecord) => {
        return res;
      });
  }

  public joinRoom(roomId) {
    // put it into cache
    return this.getRoomRecord(roomId).then(() => {
      return this.serverRestApiService.mutate({
        query: joinRoomQuery,
        variables: { roomId },
      });
    });
  }

  public leaveRoom(roomId) {
    return this.serverRestApiService.mutate({
      query: leaveRoomQuery,
      variables: { roomId },
    });
  }

  public editAccountPermissionModerator(resourceId, accountId, value: boolean) {
    return this.serverRestApiService.mutate({
      query: editRoomAccountPermissionQuery,
      variables: {
        roomId: resourceId,
        accountId,
        attribute: 'msgBrdModerate',
        value,
      },
    });
  }

  public getRoomMembers(roomId): Promise<RoomMemberRecord> {
    return this.serverRestApiService.query({
      query: roomMemberQuery,
      variables: {
        id: roomId,
      },
    });
  }

  public preloadRooms(): Promise<RoomWithData[]> {
    return this.serverRestApiService
      .query({ query: getAllRoomQuery })
      .then((rooms: RoomRecord[]) => {
        const promises: Promise<RoomWithData>[] = [];
        for (let i = 0; i < rooms.length; i++) {
          ((i) => {
            promises.push(
              this.getRoom(rooms[i].id).catch((err) => {
                console.warn('preload room err', err);
                return null;
              })
            );
          })(i);
        }
        return Promise.all(promises);
      });
  }

  public getDetail(roomId): Promise<RoomDetail> {
    return this.serverRestApiService.query({
      query: getRoomDetailQuery,
      variables: {
        id: roomId,
      },
    });
  }

  public getPublicShareUrl(resourceId: string): Promise<string> {
    console.log('get public share url: ', resourceId);
    return this.getKeyring(resourceId).then((r) => {
      // TODO replace _keys with nicer path
      return (
        environment.site_base +
        '/room/' +
        resourceId +
        '/chat/room#' +
        this.routerHandler.fragmentToRaw({
          roomkey: base64.stringify(r['_keys'][AbstractRoomKeyring.PINNED]),
        })
      );
    });
  }
}
