import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { marker } from '@biesbjerg/ngx-translate-extract-marker';
import { base64 } from 'rfc4648';
import { Observable } from 'rxjs';
import {
  GroupConfigBlockFlags,
  GroupConfigBlockMemberFlags,
} from 'src/app/shared/crypto/group_config/block';
import {
  MemberPermissions,
  FlagParser,
  RoomPermissions,
} from 'src/app/shared/crypto/group_config/chain-flag-parser';
import { AbstractRoomKeyring } from 'src/app/shared/crypto/keyring/room_base';
import { GeneralConfirmDialogComponent } from 'src/app/shared/dialogs/general-confirm-dialog/general-confirm-dialog.component';
import { ImageCropperDialogComponent } from 'src/app/shared/dialogs/image-cropper-dialog/image-cropper-dialog.component';
import {
  InviteUserDialogComponent,
  InviteUserDialogData,
} from 'src/app/shared/dialogs/invite-user-dialog/invite-user-dialog.component';
import { ComponentCanDeactivate } from 'src/app/shared/guards/pending-changes.guard';
import { AccountService } from 'src/app/shared/server-services/account.service';
import { NanoService } from 'src/app/shared/nano/nano.service';
import { AccountAvatarRecord } from 'src/app/shared/server-services/query-records/account-records';
import {
  PermissionDriveAccess,
  PermissionRole,
  RoomData,
  RoomMember,
  RoomMyPermissionAccountRecord,
} from 'src/app/shared/server-services/query-records/room-records';
import { RoomPermissionAttributes, RoomService } from 'src/app/shared/server-services/room.service';
import { SubscriptionService } from 'src/app/shared/server-services/subscription.service';
import { DialogService } from 'src/app/shared/services/dialog.service';
import { RouterHandler } from 'src/app/shared/services/router-handler.service';
import { environment } from 'src/environments/environment';
import { WeightedSearch } from 'src/app/shared/weighted-search';
import { ClipboardService } from 'src/app/shared/services/clipboard.service';
import { ObjectSorter } from 'src/app/shared/object-sorter';
import { SnackBarService } from 'src/app/shared/services/snackbar.service';
import { AuthService } from 'src/app/shared/server-services/auth.service';
import { RoomConfigurationDialogComponent } from 'src/app/shared/dialogs/room-configuration-dialog/room-configuration-dialog.component';

@Component({
  selector: 'app-room-details',
  templateUrl: './room-details.component.html',
  styleUrls: ['./room-details.component.scss'],
})
export class RoomDetailsComponent implements OnInit, OnDestroy, ComponentCanDeactivate {
  public resourceId;
  public isNanoConnected: boolean;

  public resourceName: string;
  public oldResourceName: Object; // detect changes
  public avatar: ArrayBuffer;

  public allowAnonym: boolean;
  public oldAllowAnonym: boolean;

  public msgBrdReadOnly: boolean;
  public oldMsgBrdReadOnly: boolean;

  public noReactions: boolean;
  public oldNoReactions: boolean;

  public members: RoomMember[] = [];
  public showMembers: RoomMember[] = []; // display with filter
  public selectedMember: RoomMember = null;
  public amIAdmin: boolean = false;
  public amIOwner: boolean = false;

  public ownerAccountId;
  public myPermissions: RoomMyPermissionAccountRecord;

  public showUrl: boolean = false;
  public publicUrl: string = '';

  public isUnsavedChange: boolean = false;
  public forceDeactivate: boolean = false;
  public saving: boolean = false;

  public maxMembers: number;

  public canIEditPermissions = this.calcEditPermissions();

  private memberFilter = '';
  private me = null;

  constructor(
    private routerHandler: RouterHandler,
    private accountService: AccountService,
    private roomService: RoomService,
    private subscriptionService: SubscriptionService,
    private nanoService: NanoService,
    public dialog: MatDialog,
    private dialogService: DialogService,
    private clipboardService: ClipboardService,
    private snackbarService: SnackBarService,
    private authService: AuthService
  ) {
    this.accountService.getMe().then((res) => {
      this.me = res;
    });
  }

  @HostListener('window:beforeunload')
  canDeactivate(e): Observable<boolean> | boolean {
    return !this.isUnsavedChange;
  }

  public calcEditPermissions() {
    return (this.amIAdmin && this.isNanoConnected) || this.amIOwner;
  }

  public copy() {
    this.clipboardService
      .copy(this.publicUrl)
      .then(() => {
        this.snackbarService.showSnackbar(marker('Url copied to clipboard'));
      })
      .catch((err) => {
        console.error('clipboard err', err);
        this.dialogService.openAlertDialog(
          marker('Copy Error'),
          marker('Could not copy to the clipboard')
        );
      });
  }

  public onRouterChange = async (route) => {
    this.resourceId = route.params.id;

    this.subscriptionService.subscribeWorkspaceLoaded(() => {
      this.configDetailsPage();
    });
  };

  public onChange(event?) {
    this.isUnsavedChange = true;
  }

  ngOnInit(): void {
    this.memberFilter = '';
    if (this.routerHandler.isOnPage('room/?/details')) {
      this.onRouterChange(this.routerHandler.getRoute());
    }
    this.routerHandler.subscribe('room/?/details', this.onRouterChange);
  }

  ngOnDestroy() {
    this.routerHandler.unsubscribe('room/?/details', this.onRouterChange);
  }

  public getConfig(): Promise<RoomMember[]> {
    return this.roomService.getRoomConfig(this.resourceId).then((res: FlagParser) => {
      let map = res.getMembersPermissions();
      let tmpUsers: RoomMember[] = [];

      let avatarIds = Array.from(map.keys());

      return this.accountService.getAccounts(avatarIds).then((accountAvatars) => {
        for (var id in accountAvatars) {
          tmpUsers.push(new RoomMember(accountAvatars[id], map.get(id), false));
        }
        return tmpUsers;
      });
    });
  }

  public selectMember(m: RoomMember) {
    if (this.selectedMember === m) {
      this.selectedMember = null;
    } else {
      this.selectedMember = m;
    }
  }

  public filterUser(s) {
    this.memberFilter = s;
    if (!this.memberFilter) {
      this.showMembers = this.members;
    } else {
      this.showMembers = WeightedSearch.search(
        this.memberFilter,
        ['user.avatarName'],
        this.members
      );
    }
  }

  public openInviterDialog() {
    let passUsers = [];

    this.members.forEach((u) => {
      passUsers.push(u.user);
    });

    const dialogRef = this.dialog.open<
      InviteUserDialogComponent,
      InviteUserDialogData,
      AccountAvatarRecord[]
    >(InviteUserDialogComponent, {
      data: {
        member: passUsers,
        currentMember: passUsers.length,
        maxMember: this.maxMembers,
      },
    });

    dialogRef.afterClosed().subscribe((result) => {
      console.log('invited users', result);
      if (!result) {
        return;
      }
      if (result.length > 0) {
        this.isUnsavedChange = true;
      }

      result.forEach((user) => {
        this.members.unshift(
          new RoomMember(user, {
            canAdhocDrive: false,
            canWriteDrive: false,
            isActive: true,
            isAdmin: false,
            isModerator: false,
            canUploadDrive: false,
          })
        );
      });
      this.filterUser(''); // reset filter and show object
    });
  }

  save() {
    if (this.saving) return;
    this.saving = true;

    let p: Promise<any>[] = [];

    // these props can be changed via restapi
    if (this.resourceName != this.oldResourceName) {
      p.push(
        new Promise((resolve, reject) => {
          this.roomService
            .editData(this.resourceId, this.resourceName, this.avatar)
            .then(() => {
              this.oldResourceName = this.resourceName;
              resolve(true);
            })
            .catch(() => {
              reject(new Error('could not change the resource name'));
            });
        })
      );
    }

    if (this.msgBrdReadOnly != this.oldMsgBrdReadOnly) {
      p.push(
        new Promise((resolve, reject) => {
          this.roomService
            .editPermission(
              this.resourceId,
              RoomPermissionAttributes.MESSAGE_BOARD_READ_ONLY,
              this.msgBrdReadOnly
            )
            .then(() => {
              this.oldMsgBrdReadOnly = this.msgBrdReadOnly;
              resolve(true);
            })
            .catch(() => {
              reject(new Error('could not change the msg board read only flag for the room'));
            });
        })
      );
    }

    if (this.noReactions != this.oldNoReactions) {
      p.push(
        new Promise((resolve, reject) => {
          this.roomService
            .editPermission(
              this.resourceId,
              RoomPermissionAttributes.NO_REACTIONS,
              this.noReactions
            )
            .then(() => {
              this.oldNoReactions = this.noReactions;
              resolve(true);
            })
            .catch(() => {
              reject(new Error('could not change the msg board read only flag for the room'));
            });
        })
      );
    }

    if (this.canIEditPermissions) {
      // these props need config block to change
      let roomPerm: RoomPermissions = {};
      if (this.oldAllowAnonym != this.allowAnonym) {
        roomPerm.allowAnonym = this.allowAnonym;
      }

      if (this.isNanoConnected) {
        let changes = {};
        this.members.forEach((m) => {
          changes[m.user.id] = FlagParser.parseToFlags(m.permissions, m.newPermissions);
        });

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

        p.push(
          new Promise((resolve, reject) => {
            this.nanoService.nanoCreateRoomConfigBlock(
              this.resourceId,
              changes,
              roomFlag,
              (res) => {
                this.oldAllowAnonym = this.allowAnonym;
                resolve(true);
              },
              (err) => {
                console.log('can not set the permissions for the room via nano', err);
                reject(err);
              }
            );
          })
        );
      } else {
        // only owner
        p.push(
          new Promise((resolve, reject) => {
            this.roomService
              .saveConfig(this.resourceId, this.members, roomPerm)
              .then(() => {
                this.oldAllowAnonym = this.allowAnonym;
                resolve(true);
              })
              .catch((err) => {
                console.error('can not save the permissions for the room via rest', err);
                reject(err);
              });
          })
        );
      }
    }

    Promise.all(p)
      .then(() => {
        let permissionChangePromises: Promise<boolean>[] = [];

        // the edit moderator must be called seperately and only via rest
        this.members.forEach((m) => {
          if (!m.user.deleted) {
            if (m.user.id !== this.me.id) {
              if (m.permissions.isModerator != m.newPermissions.isModerator) {
                permissionChangePromises.push(
                  new Promise((resolve, reject) => {
                    this.roomService
                      .editAccountPermissionModerator(
                        this.resourceId,
                        m.user.id,
                        m.newPermissions.isModerator
                      )
                      .then(() => {
                        m.permissions.isModerator = m.newPermissions.isModerator;
                        resolve(true);
                      })
                      .catch((err) => {
                        console.error('can not set the moderator flag for this user', m, err);
                        reject(new Error('can not set the moderator flag for this user'));
                      });
                  })
                );
              }
            }
          } else {
            // clean up the deleted user
            m.newPermissions = {
              canAdhocDrive: false,
              canUploadDrive: false,
              canWriteDrive: false,
              isActive: false,
              isAdmin: false,
              isModerator: false,
            };
          }
        });
        return Promise.all(permissionChangePromises).then(() => {
          this.snackbarService.showSnackbar(marker('Save Successful.'));
          this.isUnsavedChange = false;
          this.members.forEach((m) => {
            m.permissions = Object.assign({}, m.newPermissions);
          });
        });
      })
      .catch((onrejected) => {
        console.error('error during the save process', onrejected);
        if (onrejected.message === 'Message error: Loaded config-blockchain would require merge') {
          this.dialogService.openAlertDialog(
            marker('Save error'),
            marker('Parallel changes conflicted with your request. Please, try again.')
          );
        } else if (onrejected.message === 'can not set the moderator flag for this user') {
          this.dialogService.openAlertDialog(
            marker('Save error'),
            marker('Error happened during room permission save. Some changes may have been lost.')
          );
        } else {
          this.dialogService.openAlertDialog(
            marker('Save error'),
            marker('Error during the save.')
          );
        }
      })
      .finally(() => {
        this.saving = false;
      });
  }

  leaveRoom() {
    var ref = this.dialog.open(GeneralConfirmDialogComponent, {
      data: {
        title: this.resourceName,
        text: marker('Are you sure you want to leave?'),
      },
    });

    ref.afterClosed().subscribe((closedWithOK: boolean) => {
      if (closedWithOK) {
        this.roomService.leaveRoom(this.resourceId).then((res) => {
          //console.log("done", res);
          this.routerHandler.navigate(['/']);
        });
      }
    });
  }

  public deleteRoom() {
    var ref = this.dialog.open(GeneralConfirmDialogComponent, {
      data: {
        title: this.resourceName,
        text: marker('Are you sure you want to delete this room?'),
      },
    });

    ref.afterClosed().subscribe((closedWithOK: boolean) => {
      if (closedWithOK) {
        this.roomService.deleteRoom(this.resourceId).then((res) => {
          //console.log("done", res);
          this.routerHandler.navigate(['/']);
        });
      }
    });
  }

  public openEditAvatarDialog(): void {
    var ref = this.dialog.open(ImageCropperDialogComponent);
    ref.afterClosed().subscribe((avatarImageBlob: Blob) => {
      if (!avatarImageBlob) return;

      avatarImageBlob.arrayBuffer().then((res: ArrayBuffer) => {
        this.roomService
          .editData(this.resourceId, this.resourceName, res)
          .then(() => {
            avatarImageBlob.arrayBuffer().then((buffer) => {
              this.avatar = buffer;
            });
            console.log('ok');
            this.snackbarService.showSnackbar(marker('Room avatar has changed.'));
          })
          .catch(() => {
            console.error('not ok');
          });
      });
    });
  }

  public changePermission(m: RoomMember, role: PermissionRole) {
    if (role == PermissionRole.MEMBER) {
      m.newPermissions.isAdmin = false;
      m.newPermissions.isModerator = false;
    } else if (role == PermissionRole.MODERATOR) {
      m.newPermissions.isAdmin = false;
      m.newPermissions.isModerator = true;
    } else if (role == PermissionRole.ADMIN) {
      m.newPermissions.isAdmin = true;
      m.newPermissions.isModerator = true;
    }
    this.onChange();
  }

  public changeDriveAccess(m: RoomMember, access: PermissionDriveAccess) {
    if (access == PermissionDriveAccess.FULL_ACCESS) {
      m.newPermissions.canWriteDrive = true;
      m.newPermissions.canUploadDrive = true;
    } else if (access == PermissionDriveAccess.UPLOAD_ONLY) {
      m.newPermissions.canWriteDrive = false;
      m.newPermissions.canUploadDrive = true;
    }
    if (access == PermissionDriveAccess.READ_ONLY) {
      m.newPermissions.canWriteDrive = false;
      m.newPermissions.canUploadDrive = false;
    }
    this.onChange();
  }

  public configDetailsPage = async () => {
    // get the room data
    await this.roomService.getRoom(this.resourceId).then(async (roomData) => {
      if (!roomData || roomData.inaccessible) return;

      this.isNanoConnected = Object.keys(roomData['nanoSessions']).length == 1;
      this.canIEditPermissions = this.calcEditPermissions();
      this.resourceName = (<RoomData>roomData.data)?.name;
      this.oldResourceName = this.resourceName;
      this.avatar = (<RoomData>roomData.data)?.avatar;

      // get my permissions, if im a granted/member/admin/owner i can get the account with the roomPermissions
      await this.roomService.getMyRoomPermissions(this.resourceId).then(async (myPerm) => {
        // get the room flags
        this.allowAnonym = myPerm.allowAnonymous;
        this.oldAllowAnonym = myPerm.allowAnonymous;
        this.msgBrdReadOnly = myPerm.msgBrdReadOnly;
        this.oldMsgBrdReadOnly = myPerm.msgBrdReadOnly;
        this.noReactions = myPerm.noReactions;
        this.oldNoReactions = myPerm.noReactions;
        this.ownerAccountId = myPerm.ownerAccountId;
        this.myPermissions = myPerm.account;

        this.amIAdmin = !!myPerm.account?.administrator;
        this.canIEditPermissions = this.calcEditPermissions();

        // there is an account on the response, so i have permission to call the getRoomMembers
        if (myPerm.account) {
          this.amIOwner = this.me.id == myPerm.ownerAccountId;
          this.canIEditPermissions = this.calcEditPermissions();

          await this.roomService
            .getRoomMembers(this.resourceId)
            .then(async (roomMembers) => {
              let members: { [key: string]: RoomMember } = {};

              let accountIds = roomMembers.accounts.map((a) => a.id);
              let accountAvatars = await this.accountService.getAccounts(accountIds);
              roomMembers.accounts.forEach((acc) => {
                let accountAvatar = accountAvatars[acc.id];
                members[acc.id] = new RoomMember(
                  accountAvatar,
                  {
                    canAdhocDrive: null, // this coming from the getConfig
                    canWriteDrive: null, // getConfig
                    isActive: true, // eq with null or false
                    isAdmin: acc?.administrator,
                    isModerator: acc?.msgBrdModerate === true,
                    canUploadDrive: null,
                  },
                  false
                );
              });

              // can not get these parameters if im not admin or owner
              members[this.me.id].permissions.canAdhocDrive = myPerm.account.driveAdhoc;
              members[this.me.id].permissions.canUploadDrive = myPerm.account.driveUpload;
              members[this.me.id].permissions.canWriteDrive = myPerm.account.driveWrite;
              members[this.me.id].newPermissions.canAdhocDrive = myPerm.account.driveAdhoc;
              members[this.me.id].newPermissions.canUploadDrive = myPerm.account.driveUpload;
              members[this.me.id].newPermissions.canWriteDrive = myPerm.account.driveWrite;

              return members;
            })
            .then((members) => {
              if (this.amIOwner || this.amIAdmin) {
                return this.roomService.getRoomPermissions(this.resourceId).then((roomPerm) => {
                  this.maxMembers = roomPerm.maxMembers;
                  for (let i = 0; i < roomPerm.accounts.length; i++) {
                    let acc = roomPerm.accounts[i];

                    members[acc.id].permissions.isActive = !acc.rejected;
                    members[acc.id].newPermissions.isActive = !acc.rejected;
                  }

                  return this.getConfig().then((accounts) => {
                    // the moderate come only from the roomPermissions
                    accounts.forEach((acc) => {
                      // theres a chance, the rest response doesnt contain the leaved member
                      acc.permissions.isModerator = members[acc.user.id]
                        ? members[acc.user.id].permissions.isModerator
                        : false;
                      acc.newPermissions.isModerator = acc.permissions.isModerator;
                    });
                    this.members = ObjectSorter.sort(accounts, 'user.avatarName');
                    if (!this.memberFilter) {
                      this.showMembers = this.members;
                    } else {
                      this.showMembers = WeightedSearch.search(
                        this.memberFilter,
                        ['user.avatarName'],
                        this.members
                      );
                    }
                    return;
                  });
                });
              } else {
                this.members = Object.values(members);
                ObjectSorter.sort(this.members, 'user.avatarName');
                if (!this.memberFilter) {
                  this.showMembers = this.members;
                } else {
                  this.showMembers = WeightedSearch.search(
                    this.memberFilter,
                    ['user.avatarName'],
                    this.members
                  );
                }

                return;
              }
            });
        }
      });

      this.roomService.getPublicShareUrl(this.resourceId).then((r) => {
        // TODO replace _keys with nicer path
        this.publicUrl = r;
      });
    });
  };

  /* 
  // for debug
  public _getConfig() {
    this.roomService.getRoomConfig(this.resourceId).then((r) => console.log(r));
  }

  public _getPermissions() {
    this.roomService
      .getRoomPermissions(this.resourceId)
      .then((r) => console.log(r));
  }

  public _getMyPermissions() {
    this.roomService
      .getMyRoomPermissions(this.resourceId)
      .then((r) => console.log(r));
  }

  public _getMembers() {
    this.roomService
      .getRoomMembers(this.resourceId)
      .then((r) => console.log(r));
  }
  */

  public openRoomConfigurationDialog() {
    this.dialog.open(RoomConfigurationDialogComponent, {
      data: this.resourceId,
    });
  }
}
