import {
  bufferToBase64,
  isWebauthnSupported,
  stringToBuffer,
} from "discourse/lib/webauthn";
import Controller from "@ember/controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";

// model for this controller is user
export default Controller.extend(ModalFunctionality, {
  loading: false,
  errorMessage: null,

  onShow() {
    // clear properties every time because the controller is a singleton
    this.setProperties({
      errorMessage: null,
      loading: true,
      securityKeyName: I18n.t("user.second_factor.security_key.default_name"),
      webauthnUnsupported: !isWebauthnSupported(),
    });

    this.model
      .requestSecurityKeyChallenge()
      .then((response) => {
        if (response.error) {
          this.set("errorMessage", response.error);
          return;
        }

        this.setProperties({
          errorMessage: isWebauthnSupported()
            ? null
            : I18n.t("login.security_key_support_missing_error"),
          loading: false,
          challenge: response.challenge,
          relayingParty: {
            id: response.rp_id,
            name: response.rp_name,
          },
          supported_algorithms: response.supported_algorithms,
          user_secure_id: response.user_secure_id,
          existing_active_credential_ids:
            response.existing_active_credential_ids,
        });
      })
      .catch((error) => {
        this.send("closeModal");
        this.onError(error);
      })
      .finally(() => this.set("loading", false));
  },

  actions: {
    registerSecurityKey() {
      if (!this.securityKeyName) {
        this.set(
          "errorMessage",
          I18n.t("user.second_factor.security_key.name_required_error")
        );
        return;
      }
      const publicKeyCredentialCreationOptions = {
        challenge: Uint8Array.from(this.challenge, (c) => c.charCodeAt(0)),
        rp: {
          name: this.relayingParty.name,
          id: this.relayingParty.id,
        },
        user: {
          id: Uint8Array.from(this.user_secure_id, (c) => c.charCodeAt(0)),
          displayName: this.model.username_lower,
          name: this.model.username_lower,
        },
        pubKeyCredParams: this.supported_algorithms.map((alg) => {
          return { type: "public-key", alg: alg };
        }),
        excludeCredentials: this.existing_active_credential_ids.map(
          (credentialId) => {
            return {
              type: "public-key",
              id: stringToBuffer(atob(credentialId)),
            };
          }
        ),
        timeout: 20000,
        attestation: "none",
        authenticatorSelection: {
          // see https://chromium.googlesource.com/chromium/src/+/master/content/browser/webauth/uv_preferred.md for why
          // default value of preferred is not necesarrily what we want, it limits webauthn to only devices that support
          // user verification, which usually requires entering a PIN
          userVerification: "discouraged",
        },
      };

      navigator.credentials
        .create({
          publicKey: publicKeyCredentialCreationOptions,
        })
        .then(
          (credential) => {
            let serverData = {
              id: credential.id,
              rawId: bufferToBase64(credential.rawId),
              type: credential.type,
              attestation: bufferToBase64(
                credential.response.attestationObject
              ),
              clientData: bufferToBase64(credential.response.clientDataJSON),
              name: this.securityKeyName,
            };

            this.model
              .registerSecurityKey(serverData)
              .then((response) => {
                if (response.error) {
                  this.set("errorMessage", response.error);
                  return;
                }
                this.markDirty();
                this.set("errorMessage", null);
                this.send("closeModal");
              })
              .catch((error) => this.onError(error))
              .finally(() => this.set("loading", false));
          },
          (err) => {
            if (err.name === "InvalidStateError") {
              return this.set(
                "errorMessage",
                I18n.t("user.second_factor.security_key.already_added_error")
              );
            }
            if (err.name === "NotAllowedError") {
              return this.set(
                "errorMessage",
                I18n.t("user.second_factor.security_key.not_allowed_error")
              );
            }
            this.set("errorMessage", err.message);
          }
        );
    },
  },
});
