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 } 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 { RoomMyPermissionAccountRecord } from "src/app/shared/server-services/query-records/room-records";
import {
  RoomService,
  RoomData,
} 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";

export enum PermissionRole {
  ADMIN = "admin",
  MODERATOR = "moderator",
  MEMBER = "member",
}

export enum PermissionDriveAccess {
  FULL_ACCESS = "full_access",
  UPLOAD_ONLY = "upload_only",
  READ_ONLY = "read_only",
}
export class RoomMember {
  public user: AccountAvatarRecord;
  public permissions: MemberPermissions;
  public newMember: boolean;
  public newPermissions: MemberPermissions;

  constructor(
    user: AccountAvatarRecord,
    permissions: MemberPermissions,
    newMember: boolean = true
  ) {
    this.user = user;

    if (newMember) {
      // the "old" values set to false
      this.permissions = <any>{};
      for (let key in permissions) {
        this.permissions[key] = false;
      }
    } else {
      this.permissions = Object.assign({}, permissions);
    }

    this.newPermissions = Object.assign({}, permissions);
  }

  public get hasChanged(): boolean {
    if (this.newMember) return true;
    for (let key in this.permissions) {
      if (this.permissions[key] != this.newPermissions[key]) {
        return true;
      }
    }
    return false;
  }
}

@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 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;

  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
  ) {}

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

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

  public copy() {
    this.clipboardService
      .copy(this.publicUrl)
      .then(() => {
        this.dialogService.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(() => {
      // get the room data
      this.roomService.getData(this.resourceId).then((roomData) => {
        if (!roomData || roomData.inaccessible) return;

        this.isNanoConnected =
          Object.keys(roomData["nanoSessions"]).length == 1;
        this.resourceName = (<RoomData>roomData.data)?.name;
        this.oldResourceName = this.resourceName;
        this.avatar = (<RoomData>roomData.data)?.avatar;
        //console.log("roomData", roomData);

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

            this.amIAdmin = myPerm.account.administrator;

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

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

                    for (let i = 0; i < roomMembers.accounts.length; i++) {
                      let acc = roomMembers.accounts[i];
                      members[acc.id] = new RoomMember(
                        await this.accountService.getAccount(acc.id),
                        {
                          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[me.id].permissions.canAdhocDrive =
                      myPerm.account.driveAdhoc;
                    members[me.id].permissions.canUploadDrive =
                      myPerm.account.driveUpload;
                    members[me.id].permissions.canWriteDrive =
                      myPerm.account.driveWrite;
                    members[me.id].newPermissions.canAdhocDrive =
                      myPerm.account.driveAdhoc;
                    members[me.id].newPermissions.canUploadDrive =
                      myPerm.account.driveUpload;
                    members[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;
                            });
                            console.log("accs", accounts);
                            this.members = accounts;
                            this.showMembers = accounts;

                            return;
                          });
                        });
                    } else {
                      this.members = Object.values(members);
                      this.showMembers = this.members;
                      return;
                    }
                  });
              });
            }
          });

        this.roomService.getKeyring(this.resourceId).then((r) => {
          // TODO replace _keys with nicer path
          this.publicUrl =
            environment.site_base +
            "/room/" +
            this.resourceId +
            "/chat/room#" +
            this.routerHandler.fragmentToRaw({
              roomkey: base64.stringify(r["_keys"][AbstractRoomKeyring.PINNED]),
            });
        });
      });
    });
  };

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

  ngOnInit(): void {
    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 promises: Promise<any>[] = [];
        map.forEach((value, key) => {
          promises.push(
            this.accountService.getAccount(key).then((user) => {
              tmpUsers.push(new RoomMember(user, value, false));
            })
          );
        });

        return Promise.all(promises).then((allValue) => {
          //console.log(tmpUsers);
          return tmpUsers;
        });
      });
  }

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

  public filterUser(s) {
    this.showMembers = WeightedSearch.search(
      s,
      ["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.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, 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"
                )
              );
            });
        })
      );
    }

    // the edit moderator must be called seperately and only via rest
    this.members.forEach((m) => {
      if (m.permissions.isModerator != m.newPermissions.isModerator) {
        p.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")
                );
              });
          })
        );
      }
    });

    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;
        }

        //console.log("new changes", changes);

        p.push(
          new Promise((resolve, reject) => {
            this.nanoService.nanoCreateRoomConfigBlock(
              this.resourceId,
              changes,
              roomFlag,
              (res) => {
                //console.log("save details", res);
                this.oldAllowAnonym = this.allowAnonym;
                this.members.forEach((m) => {
                  m.permissions = Object.assign({}, m.newPermissions);
                });
                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;
                this.members.forEach((m) => {
                  m.permissions = Object.assign({}, m.newPermissions);
                });
                resolve(true);
              })
              .catch((err) => {
                console.error(
                  "can not save the permissions for the room via rest",
                  err
                );
                reject(err);
              });
          })
        );
      }
    }

    Promise.all(p)
      .then(() => {
        this.dialogService.showSnackbar(marker("Save Successful."));
        this.isUnsavedChange = false;
      })
      .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"),
            marker(
              "Parallel changes conflicted with your request. Please, try again."
            )
          );
        } else {
          this.dialogService.openAlertDialog(
            marker("Save"),
            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.dialogService.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();
  }

  /* 
  // 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));
  }
  */
}
