// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/webauthn/user_actions.h"

#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <variant>
#include <vector>

#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/strcat.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "device/fido/public/fido_constants.h"
#include "device/fido/public/fido_types.h"
#include "ui/gfx/vector_icon_types.h"

namespace webauthn::user_actions {

namespace {

using Mechanism = AuthenticatorRequestDialogModel::Mechanism;
using AuthenticatorType = device::AuthenticatorType;

constexpr std::string_view kGpmAndICloudAndProfile = "GpmAndICloudAndProfile";

constexpr std::string_view kGpmAndICloud = "GpmAndICloud";
constexpr std::string_view kGpmAndWin = "GpmAndWin";
constexpr std::string_view kGpmAndProfile = "GpmAndProfile";
constexpr std::string_view kICloudAndProfile = "ICloudAndProfile";

constexpr std::string_view kGpmOnly = "GpmOnly";
constexpr std::string_view kICloudOnly = "ICloudOnly";
constexpr std::string_view kWinOnly = "WinOnly";
constexpr std::string_view kProfileOnly = "ProfileOnly";
constexpr std::string_view kPasswordOnly = "PasswordOnly";

constexpr std::string_view kOthers = "Others";

enum class AuthenticatorCategory {
  kGpm,
  kICloud,
  kWindows,
  kProfile,
  kPassword,
  kOther,
};

AuthenticatorCategory CategoryFromMechanism(const Mechanism& mechanism) {
  if (std::holds_alternative<Mechanism::Credential>(mechanism.type)) {
    switch (std::get<Mechanism::Credential>(mechanism.type)->source) {
      case AuthenticatorType::kEnclave:
        return AuthenticatorCategory::kGpm;
      case AuthenticatorType::kTouchID:
        return AuthenticatorCategory::kProfile;
      case AuthenticatorType::kICloudKeychain:
        return AuthenticatorCategory::kICloud;
      case AuthenticatorType::kWinNative:
        return AuthenticatorCategory::kWindows;
      case AuthenticatorType::kChromeOS:
      case AuthenticatorType::kPhone:
      case AuthenticatorType::kOther:
        return AuthenticatorCategory::kOther;
    }
  } else if (std::holds_alternative<Mechanism::Password>(mechanism.type)) {
    return AuthenticatorCategory::kPassword;
  } else if (std::holds_alternative<Mechanism::Enclave>(mechanism.type)) {
    return AuthenticatorCategory::kGpm;
  } else if (std::holds_alternative<Mechanism::WindowsAPI>(mechanism.type)) {
    return AuthenticatorCategory::kWindows;
  } else if (std::holds_alternative<Mechanism::ICloudKeychain>(
                 mechanism.type)) {
    return AuthenticatorCategory::kICloud;
  }

  return AuthenticatorCategory::kOther;
}

std::tuple<bool, bool, bool, bool> AuthenticatorsAvailable(
    const std::vector<Mechanism>& mechanisms) {
  bool has_gpm = false;
  bool has_icloud = false;
  bool has_profile = false;
  bool has_win = false;
  // TODO(derinel): Add ChromeOS combinations.
  for (const auto& mech : mechanisms) {
    switch (CategoryFromMechanism(mech)) {
      case AuthenticatorCategory::kGpm:
        has_gpm = true;
        break;
      case AuthenticatorCategory::kProfile:
        has_profile = true;
        break;
      case AuthenticatorCategory::kICloud:
        has_icloud = true;
        break;
      case AuthenticatorCategory::kWindows:
        has_win = true;
        break;
      case AuthenticatorCategory::kPassword:
      case AuthenticatorCategory::kOther:
        break;
    }
  }
  return {has_gpm, has_icloud, has_profile, has_win};
}

}  // namespace

void RecordMultipleOptionsShown(const std::vector<Mechanism>& mechanisms,
                                device::FidoRequestType request_type) {
  bool has_gpm = false;
  bool has_icloud = false;
  bool has_profile = false;
  bool has_win = false;
  std::tie(has_gpm, has_icloud, has_profile, has_win) =
      AuthenticatorsAvailable(mechanisms);

  std::string_view metric_to_emit;
  if (has_gpm && has_icloud && has_profile) {
    metric_to_emit = kGpmAndICloudAndProfile;
  } else if (has_gpm && has_icloud) {
    metric_to_emit = kGpmAndICloud;
  } else if (has_gpm && has_win) {
    metric_to_emit = kGpmAndWin;
  } else if (has_gpm && has_profile) {
    metric_to_emit = kGpmAndProfile;
  } else if (has_gpm) {
    metric_to_emit = kGpmOnly;
  } else if (has_icloud && has_profile) {
    metric_to_emit = kICloudAndProfile;
  } else if (has_icloud) {
    metric_to_emit = kICloudOnly;
  } else if (has_win) {
    metric_to_emit = kWinOnly;
  } else if (has_profile) {
    metric_to_emit = kProfileOnly;
  } else {
    metric_to_emit = kOthers;
  }

  std::string metric;
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      metric = base::StrCat(
          {"WebAuthn.MakeCredential.MultipleOptionsShown.", metric_to_emit});
      break;
    case device::FidoRequestType::kGetAssertion:
      metric = base::StrCat(
          {"WebAuthn.GetAssertion.MultipleOptionsShown.", metric_to_emit});
      break;
  }
  base::RecordAction(base::UserMetricsAction(metric.c_str()));
}

void RecordPriorityOptionShown(const Mechanism& mechanism) {
  std::optional<std::string_view> metric_to_emit;
  switch (CategoryFromMechanism(mechanism)) {
    case AuthenticatorCategory::kGpm:
      metric_to_emit = kGpmOnly;
      break;
    case AuthenticatorCategory::kProfile:
      metric_to_emit = kProfileOnly;
      break;
    case AuthenticatorCategory::kICloud:
      metric_to_emit = kICloudOnly;
      break;
    case AuthenticatorCategory::kWindows:
      metric_to_emit = kWinOnly;
      break;
    case AuthenticatorCategory::kPassword:
    case AuthenticatorCategory::kOther:
      break;
  }

  if (metric_to_emit.has_value()) {
    base::RecordAction(base::UserMetricsAction(
        base::StrCat(
            {"WebAuthn.GetAssertion.PriorityOptionShown.", *metric_to_emit})
            .c_str()));
  }
}

void RecordCombinedSelectorShown() {
  base::RecordAction(
      base::UserMetricsAction("WebAuthn.GetAssertion.CombinedSelectorShown"));
}

void RecordHybridAndSecurityKeyDialogShown(
    device::FidoRequestType request_type) {
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      base::RecordAction(base::UserMetricsAction(
          "WebAuthn.MakeCredential.HybridAndSecurityKeyDialogShown"));
      break;
    case device::FidoRequestType::kGetAssertion:
      base::RecordAction(base::UserMetricsAction(
          "WebAuthn.GetAssertion.HybridAndSecurityKeyDialogShown"));
      break;
  }
}

void RecordSecurityKeyDialogShown(device::FidoRequestType request_type) {
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      base::RecordAction(base::UserMetricsAction(
          "WebAuthn.MakeCredential.SecurityKeyDialogShown"));
      break;
    case device::FidoRequestType::kGetAssertion:
      base::RecordAction(base::UserMetricsAction(
          "WebAuthn.GetAssertion.SecurityKeyDialogShown"));
      break;
  }
}

void RecordMechanismClick(const Mechanism& mech) {
  std::string_view metric_to_emit;
  switch (CategoryFromMechanism(mech)) {
    case AuthenticatorCategory::kGpm:
      metric_to_emit = kGpmOnly;
      break;
    case AuthenticatorCategory::kProfile:
      metric_to_emit = kProfileOnly;
      break;
    case AuthenticatorCategory::kICloud:
      metric_to_emit = kICloudOnly;
      break;
    case AuthenticatorCategory::kWindows:
      metric_to_emit = kWinOnly;
      break;
    case AuthenticatorCategory::kPassword:
      metric_to_emit = kPasswordOnly;
      break;
    case AuthenticatorCategory::kOther:
      metric_to_emit = kOthers;
      break;
  }

  base::RecordAction(base::UserMetricsAction(
      base::StrCat({"WebAuthn.Dialog.UserSelected.", metric_to_emit}).c_str()));
}

void RecordCancelClick() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.Dialog.Cancelled"));
}

void RecordAcceptClick() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.Dialog.Accepted"));
}

void RecordTrustDialogShown(device::FidoRequestType request_type) {
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      base::RecordAction(base::UserMetricsAction(
          "WebAuthn.MakeCredential.TrustGpmDialogShown"));
      break;
    case device::FidoRequestType::kGetAssertion:
      base::RecordAction(
          base::UserMetricsAction("WebAuthn.GetAssertion.TrustGpmDialogShown"));
      break;
  }
}

void RecordCreateGpmDialogShown() {
  base::RecordAction(
      base::UserMetricsAction("WebAuthn.MakeCredential.CreateGpmDialogShown"));
}

void RecordRecoveryShown(device::FidoRequestType type) {
  switch (type) {
    case device::FidoRequestType::kMakeCredential:
      base::RecordAction(
          base::UserMetricsAction("WebAuthn.MakeCredential.RecoverGpmShown"));
      break;
    case device::FidoRequestType::kGetAssertion:
      base::RecordAction(
          base::UserMetricsAction("WebAuthn.GetAssertion.RecoverGpmShown"));
      break;
  }
}

void RecordRecoveryCancelled() {
  base::RecordAction(
      base::UserMetricsAction("WebAuthn.Window.RecoverGpmCancelled"));
}

void RecordRecoverySucceeded() {
  base::RecordAction(
      base::UserMetricsAction("WebAuthn.Window.RecoverGpmSucceeded"));
}

void RecordICloudShown(device::FidoRequestType request_type) {
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      base::RecordAction(
          base::UserMetricsAction("WebAuthn.MakeCredential.ICloudShown"));
      break;
    case device::FidoRequestType::kGetAssertion:
      base::RecordAction(
          base::UserMetricsAction("WebAuthn.GetAssertion.ICloudShown"));
      break;
  }
}

void RecordICloudCancelled() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.ICloud.Cancelled"));
}

void RecordICloudSuccess() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.ICloud.Success"));
}

void RecordGpmTouchIdDialogShown(device::FidoRequestType request_type) {
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      base::RecordAction(base::UserMetricsAction(
          "WebAuthn.MakeCredential.GpmTouchIdDialogShown"));
      break;
    case device::FidoRequestType::kGetAssertion:
      base::RecordAction(base::UserMetricsAction(
          "WebAuthn.GetAssertion.GpmTouchIdDialogShown"));
      break;
  }
}

void RecordGpmPinSheetShown(device::FidoRequestType request_type,
                            bool is_pin_creation,
                            bool is_arbitrary) {
  std::string_view webauthn_request_type;
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      webauthn_request_type = "MakeCredential.";
      break;
    case device::FidoRequestType::kGetAssertion:
      webauthn_request_type = "GetAssertion.";
      break;
  }
  std::string_view pin_mode = is_pin_creation ? "GpmCreate" : "GpmEnter";
  std::string_view pin_type = is_arbitrary ? "Arbitrary" : "";

  base::RecordAction(base::UserMetricsAction(
      base::StrCat({"WebAuthn.", webauthn_request_type, pin_mode, pin_type,
                    "PinDialogShown"})
          .c_str()));
}

void RecordGpmForgotPinClick() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.Gpm.ForgotPinClicked"));
}

void RecordGpmPinOptionChangeClick() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.Gpm.PinOptionChanged"));
}

void RecordGpmLockedShown() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.Gpm.LockedDialogShown"));
}

void RecordGpmWinUvShown(device::FidoRequestType request_type) {
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      base::RecordAction(
          base::UserMetricsAction("WebAuthn.MakeCredential.GpmWinUvShown"));
      break;
    case device::FidoRequestType::kGetAssertion:
      base::RecordAction(
          base::UserMetricsAction("WebAuthn.GetAssertion.GpmWinUvShown"));
      break;
  }
}

void RecordGpmSuccess() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.Gpm.Success"));
}

void RecordGpmFailureShown() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.Gpm.Failure"));
}

void RecordChromeProfileAuthenticatorShown(
    device::FidoRequestType request_type) {
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      base::RecordAction(base::UserMetricsAction(
          "WebAuthn.MakeCredential.ChromeProfileAuthenticatorShown"));
      break;
    case device::FidoRequestType::kGetAssertion:
      base::RecordAction(base::UserMetricsAction(
          "WebAuthn.GetAssertion.ChromeProfileAuthenticatorShown"));
      break;
  }
}

void RecordChromeProfileCancelled() {
  base::RecordAction(
      base::UserMetricsAction("WebAuthn.ChromeProfile.Cancelled"));
}

void RecordChromeProfileSuccess() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.ChromeProfile.Success"));
}

void RecordWindowsHelloShown(device::FidoRequestType request_type) {
  switch (request_type) {
    case device::FidoRequestType::kMakeCredential:
      base::RecordAction(
          base::UserMetricsAction("WebAuthn.MakeCredential.WinHelloShown"));
      break;
    case device::FidoRequestType::kGetAssertion:
      base::RecordAction(
          base::UserMetricsAction("WebAuthn.GetAssertion.WinHelloShown"));
      break;
  }
}

void RecordWindowsHelloCancelled() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.WinHello.Cancelled"));
}

void RecordWindowsHelloSuccess() {
  base::RecordAction(base::UserMetricsAction("WebAuthn.WinHello.Success"));
}

void RecordContextMenuEntryClick() {
  base::RecordAction(base::UserMetricsAction(
      "WebAuthn.ContextMenu.PasskeyFromAnotherDeviceClicked"));
}

}  // namespace webauthn::user_actions
