import { Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { marker } from '@biesbjerg/ngx-translate-extract-marker';
import msgpack from 'msgpack-lite';
import { DeviceDetectorService } from 'ngx-device-detector';
import { base64 } from 'rfc4648';
import { Subject } from 'rxjs';
import { takeUntil, throttleTime } from 'rxjs/operators';
import { AppStorage } from 'src/app/shared/app-storage';
import { DeleteAccountDialogComponent } from 'src/app/shared/dialogs/delete-account-dialog/delete-account-dialog.component';
import { ImageCropperDialogComponent } from 'src/app/shared/dialogs/image-cropper-dialog/image-cropper-dialog.component';
import { MetamaskAlertDialogComponent } from 'src/app/shared/dialogs/metamask-alert-dialog/metamask-alert-dialog.component';
import { AccountService } from 'src/app/shared/server-services/account.service';
import { AuthService } from 'src/app/shared/server-services/auth.service';
import {
  DialoguePolicy,
  MeRecord,
} from 'src/app/shared/server-services/query-records/account-records';
import { WorkspaceSubscriptionAccountEventRecord } from 'src/app/shared/server-services/query-records/workspace-records';
import {
  prepareWalletProofQuery,
  submitWalletProofQuery,
} from 'src/app/shared/server-services/querys';
import {
  SubscriptionServiceEvent,
  SubscriptionServiceEventType,
} from 'src/app/shared/server-services/subscription-event';
import { SubscriptionService } from 'src/app/shared/server-services/subscription.service';
import { ClipboardService } from 'src/app/shared/services/clipboard.service';
import { CordovaService } from 'src/app/shared/services/cordova.service';
import { DialogService } from 'src/app/shared/services/dialog.service';
import { RouterHandler } from 'src/app/shared/services/router-handler.service';
import {
  ServerRestApiService,
  TwoFactorMode,
} from 'src/app/shared/services/server-rest-api.service';
import { TitleService } from 'src/app/shared/services/title.service';
import { sideMenuActiveObserver } from 'src/app/shared/shared-observers';
import { environment } from 'src/environments/environment';
import { create_passive_identity_signature } from '../../shared/crypto/auth/identity';
import { BlobService } from '../../shared/services/blob.service';
import { SnackBarService } from '../../shared/services/snackbar.service';
import { AdvancedSettingsEnum } from '../advanced-settings/advanced-settings.component';
import { ChangeContactPolicyDialogComponent } from './change-contact-policy-dialog/change-contact-policy-dialog.component';
import { ChangeEmailDialogComponent } from './change-email-dialog/change-email-dialog.component';
import { ChangePasswordDialogComponent } from './change-password-dialog/change-password-dialog.component';
import { ChangeUsernameDialogComponent } from './change-username-dialog/change-username-dialog.component';

@Component({
  selector: 'app-account-settings',
  templateUrl: './account-settings.component.html',
  styleUrls: ['./account-settings.component.scss'],
})
export class AccountSettingsComponent implements OnInit, OnDestroy {
  me: MeRecord;
  currentTwoFactorMode: TwoFactorMode;
  dialoguePolicyEnum = DialoguePolicy;
  dataLoading: boolean = true;
  showEmail: boolean = false;
  showID: boolean = false;

  private toggle2FAEvent = new Subject();
  private destroyEvent = new Subject();
  public isDesktop: boolean = false;

  public isIdentitySettingsOn: boolean =
    AppStorage.getItem(AdvancedSettingsEnum.EXP_IDENTITY_SIGNATURE) == '1';

  constructor(
    private dialog: MatDialog,
    private accountService: AccountService,
    private authService: AuthService,
    private dialogService: DialogService,
    private routerHandler: RouterHandler,
    private titleService: TitleService,
    private subscriptionService: SubscriptionService,
    private clipboardService: ClipboardService,
    private cordovaService: CordovaService,
    private blobService: BlobService,
    private snackbarService: SnackBarService,
    private serverRestApiService: ServerRestApiService,
    private deviceDetectorService: DeviceDetectorService
  ) {
    this.titleService.setTabTitle(marker('Account settings'));

    this.isDesktop = this.deviceDetectorService.deviceType == 'desktop';
  }

  onAccountChange = (ev: WorkspaceSubscriptionAccountEventRecord) => {
    this.accountService.getMe().then((data) => {
      console.log('AccountSettingsComponent', 'data', data);
      this.me = Object.assign({}, data);
      if (ev.walletAddress !== null) {
        this.snackbarService.showSnackbar(marker('Wallet connected!'));
        this.testMetamaskConnection();
      } else if (ev.walletAddress == null) {
        this.snackbarService.showSnackbar(marker('Wallet disconnected!'));
        this.testMetamaskConnection();
      }
    });
  };

  ngOnInit(): void {
    var mePromise = this.accountService.getMe();
    var twoFAPromise = this.accountService.get2FA();

    sideMenuActiveObserver.next(false);

    Promise.all([mePromise, twoFAPromise])
      .then((res) => {
        this.me = res[0];
        this.currentTwoFactorMode = res[1].mode;
        this.dataLoading = false;

        this.testMetamaskConnection();
      })
      .catch((err) => console.error(err));

    this.subscriptionService.subscribe(
      SubscriptionServiceEvent.ACCOUNT_EVENT,
      SubscriptionServiceEventType.MODIFY,
      this.onAccountChange
    );

    this.toggle2FAEvent
      .pipe(throttleTime(2000), takeUntil(this.destroyEvent))
      .subscribe((enable: boolean) => {
        this.setTwoFactorHandler(enable);
      });
  }

  ngOnDestroy() {
    this.subscriptionService.unsubscribe(
      SubscriptionServiceEvent.ACCOUNT_EVENT,
      SubscriptionServiceEventType.MODIFY,
      this.onAccountChange
    );

    this.destroyEvent.next();
  }

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

      avatarImageBlob.arrayBuffer().then((res: ArrayBuffer) => {
        this.accountService
          .setAvatar(res)
          .then((res) => {
            this.me.avatarImageKey = res;
          })
          .catch((err) => console.error('set avatar', err));
      });
    });
  }

  openEditUserNameDialog(): void {
    var ref = this.dialog.open(ChangeUsernameDialogComponent, {
      data: { userName: this.me.avatarName },
    });
    ref.afterClosed().subscribe((newUserName: string) => {
      if (!newUserName) return;

      this.accountService
        .changeAvatarName(newUserName)
        .then((res) => {
          this.snackbarService.showSnackbar(marker('Username changed successfully!'));
        })
        .catch((err) => console.error(err));
    });
  }

  openEditEmailDialog(): void {
    var ref = this.dialog.open(ChangeEmailDialogComponent);
    ref.afterClosed().subscribe((newEmail: string) => {
      if (!newEmail) return;
      this.accountService
        .prepareEditEmail(newEmail)
        .then((res) => {
          this.snackbarService.showSnackbar(
            marker('An email has been sent to your new address with a verification link!'),
            marker('OK')
          );
        })
        .catch((err) => {
          console.error(err);
          this.dialogService.openAlertDialog(
            marker('Email change'),
            marker('An email has already been sent to your new address with a verification link!')
          );
        });
    });
  }

  setTwoFactor(enabled: boolean): void {
    this.toggle2FAEvent.next(enabled);
  }

  private setTwoFactorHandler(enabled: boolean): void {
    this.accountService
      .set2FA(enabled)
      .then((res) => {
        this.accountService
          .get2FA()
          .then((res2) => {
            this.currentTwoFactorMode = res2.mode;
            if (this.currentTwoFactorMode == TwoFactorMode.NONE)
              this.snackbarService.showSnackbar(marker('Two-Factor Authentication disabled!'));
            else if (this.currentTwoFactorMode == TwoFactorMode.CODE)
              this.snackbarService.showSnackbar(marker('Two-Factor Authentication enabled!'));
          })
          .catch((err) => {
            console.log(err);
            this.dialogService.openAlertDialog(
              marker('2FA Change'),
              marker('Error during two factor authentication.'),
              err
            );
          });
      })
      .catch((err) =>
        this.dialogService.openAlertDialog(
          marker('2FA Error'),
          marker('Error during two factor authentication.'),
          err
        )
      );
  }

  openEditPasswordDialog(): void {
    this.dialogService
      .openAlertDialog(
        marker('Warning'),
        marker('You will need to re-authenticate on every web browser after a password change!')
      )
      .subscribe(() => {
        var ref = this.dialog.open(ChangePasswordDialogComponent);
        ref.afterClosed().subscribe((password: string) => {
          if (!password) return;

          this.authService
            .changePassword(password)
            .then((res) => {
              this.serverRestApiService.manuallyInitSessionThrow();
              this.routerHandler.navigate(['/']);
            })
            .catch((err) => {
              this.dialogService.openAlertDialog(
                marker('Error'),
                marker('An error happened during password change. See details.'),
                err
              );
            });
        });
      });
  }

  openEditMessagePolicyDialog(): void {
    var ref = this.dialog.open(ChangeContactPolicyDialogComponent, {
      data: this.me.dialoguePolicy,
    });
    ref.afterClosed().subscribe((selectedPolicy: number) => {
      if (selectedPolicy === undefined) return;

      this.accountService.changeDialoguePolicy(selectedPolicy).then((res) => {
        this.snackbarService.showSnackbar(marker('Contact policy changed successfully!'));
        this.me.dialoguePolicy = selectedPolicy;
      });
    });
  }

  saveRecoveryKey() {
    let name = 'CBNANO-SECRET-RECOVERY-KEY.TXT';
    if (this.cordovaService.isOnApp()) {
      this.cordovaService
        .getFileWriter(name)
        .then((writer) => {
          return writer.write(this.authService.createRecoveryKeyFile()).then(() => {
            writer.close();
            this.snackbarService.showSnackbar(marker('Downloaded:') + ': ' + name);
          });
        })
        .catch((e) => {
          console.error('Could not create the file', e);
          this.dialogService.openAlertDialog(
            marker('Download error'),
            marker('Could not create the file')
          );
        });
    } else {
      var a;
      a = document.createElement('a');
      a.href = this.blobService.createObjectURL(this.authService.createRecoveryKeyFile());
      a.download = name;
      document.body.appendChild(a);
      a.style = 'display: none';
      a.click();
      a.remove();

      this.snackbarService.showSnackbar(marker('Downloaded') + ': ' + name);
    }
  }

  copyContactId() {
    this.clipboardService
      .copy(this.me.id)
      .then(() => {
        this.snackbarService.showSnackbar(marker('Contact ID copied!'));
      })
      .catch((err) => {
        console.error('clipboard err', err);
        this.dialogService.openAlertDialog(
          marker('Copy Error'),
          marker('Could not copy to the clipboard')
        );
      });
  }

  copyIdentitySignature() {
    return create_passive_identity_signature(
      this.authService.getSelfAccountKeyring(),
      this.serverRestApiService.makeTimestamp() * 1000
    )
      .then((sig) => {
        let signatureString = base64.stringify(msgpack.encode(sig));
        return this.clipboardService.copy(signatureString).then(() => {
          return this.snackbarService.showSnackbar(marker('Identity Signature copied!'));
        });
      })
      .catch((err) => {
        console.error('clipboard err', err);
        this.dialogService.openAlertDialog(
          marker('Copy Error'),
          marker('Could not copy to the clipboard')
        );
      });
  }

  copyContactUrl() {
    this.clipboardService
      .copy(environment.site_base + '/#contact' + RouterHandler.FRAGMENT_EQUAL + this.me.id)
      .then(() => {
        this.snackbarService.showSnackbar(marker('Contact URL copied!'));
      })
      .catch((err) => {
        console.error('clipboard err', err);
        this.dialogService.openAlertDialog(
          marker('Copy Error'),
          marker('Could not copy to the clipboard')
        );
      });
  }

  public deleteAccount(): void {
    const ref = this.dialog.open(DeleteAccountDialogComponent);
    ref.afterClosed().subscribe((deleteConfirmed) => {
      if (!deleteConfirmed) {
        return;
      }
      const waitingDialogRef = this.dialogService.openWaitingDialog(
        marker('Please wait...'),
        marker('Preparing account delete')
      );
      this.authService
        .deleteAccount()
        .then((res) => {
          console.log('authService deleteAccount res', res);
        })
        .catch((err) => {
          console.error('authService deleteAccount err', err);
        })
        .finally(() => {
          waitingDialogRef.close();
        });
    });
  }

  // METAMASK

  public walletExists: boolean = false;
  public metamaskAddressHint: string;
  public metamaskFullAddress: string;
  public metamaskConnectingInProgress: boolean = false;
  public ethereum = window['ethereum'];
  public showAddress: boolean = false;

  private testMetamaskConnection(): void {
    this.walletExists = this.me.walletAddress != null;

    console.log('AccountSettingsComponent', this.me);

    if (this.walletExists) {
      this.makeHint(this.me.walletAddress);
    }
  }

  private makeHint(address): void {
    // make hint like: 0x000*****0000
    this.metamaskAddressHint =
      address.substring(0, 5) + '*****' + address.substring(address.length - 4);
    this.metamaskFullAddress = address;
  }

  public async authMetaMask(): Promise<void> {
    this.metamaskConnectingInProgress = true;

    await this.connectMetamask();

    this.metamaskConnectingInProgress = false;
  }

  private async connectMetamask(): Promise<void> {
    if (typeof this.ethereum === 'undefined') {
      this.dialog.open(MetamaskAlertDialogComponent);

      return;
    }

    // let nonceMessage = await this.serverRestApiService.mutate({query: { endpoint: "prepareWalletProof"}});
    // let nonce = nonceMessage.split("Nonce: ")[1];

    // console.log(this.ethereum.networkVersion, this.ethereum.selectedAddress);

    let metaMaskAccount: string;
    let nonceMessage: string;
    let nonce: string;
    let signature: string;

    try {
      let accounts = await this.ethereum.request({ method: 'eth_requestAccounts' });
      metaMaskAccount = accounts[0];
    } catch (e) {
      if (e.code === 4001) {
        this.snackbarService.showSnackbar(marker('You rejected the account request.'));
      } else {
        this.dialogService.openAlertDialog(
          marker('Web3 wallet account request'),
          marker(
            'Could not request Web3 wallet account. Please make sure your account is set up before trying to connect.'
          )
        );
      }
      return;
    }

    nonceMessage = await this.serverRestApiService.mutate({
      query: prepareWalletProofQuery,
    });
    nonce = nonceMessage.split('Id: ')[1];

    try {
      signature = await this.ethereum.request({
        method: 'personal_sign',
        params: [nonceMessage, metaMaskAccount],
      });
    } catch (e) {
      console.error(e);
      return;
    }

    await this.serverRestApiService.mutate({
      query: submitWalletProofQuery,
      variables: { nonce: nonce, signature: signature },
    });

    this.snackbarService.showSnackbar(marker('Web3 wallet connected!'));
    this.testMetamaskConnection();
  }
}
