// 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/webauthn/change_pin_controller_impl.h"

#include <memory>
#include <string>
#include <utility>

#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "chrome/browser/webauthn/enclave_manager.h"
#include "chrome/browser/webauthn/enclave_manager_factory.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"

using Step = AuthenticatorRequestDialogModel::Step;

ChangePinControllerImpl::ChangePinControllerImpl(
    content::RenderFrameHost* render_frame_host)
    : content::DocumentUserData<ChangePinControllerImpl>(render_frame_host) {
  Profile* profile =
      Profile::FromBrowserContext(render_frame_host->GetBrowserContext());
  enclave_manager_ =
      EnclaveManagerFactory::GetAsEnclaveManagerForProfile(profile);
  model_ =
      base::MakeRefCounted<AuthenticatorRequestDialogModel>(render_frame_host);
  model_observation_.Observe(model_.get());
}

ChangePinControllerImpl::~ChangePinControllerImpl() {
  if (!notify_pin_change_callback_.is_null()) {
    std::move(notify_pin_change_callback_).Run(false);
  }
}

void ChangePinControllerImpl::IsChangePinFlowAvailable(
    PinAvailableCallback callback) {
  if (enclave_manager_->IsLoaded()) {
    NotifyPinAvailability(std::move(callback));
    return;
  }
  enclave_manager_->Load(
      base::BindOnce(&ChangePinControllerImpl::NotifyPinAvailability,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ChangePinControllerImpl::StartChangePin(SuccessCallback callback) {
  if (notify_pin_change_callback_ || !enclave_manager_->IsRegistered()) {
    std::move(callback).Run(false);
    return;
  }
  notify_pin_change_callback_ = std::move(callback);
  model_->SetStep(Step::kGPMReauthForPinReset);
  RecordHistogram(EnclaveChangePinEvent::kFlowStartedFromSettings);
}

void ChangePinControllerImpl::CancelAuthenticatorRequest() {
  // User clicked "Cancel" in the GPM dialog.
  Reset(/*success=*/false);
  RecordHistogram(EnclaveChangePinEvent::kNewPinCancelled);
}

void ChangePinControllerImpl::OnGPMReauthComplete(std::string rapt) {
  if (!enclave_manager_->IsRegistered()) {
    model_->SetStep(Step::kGPMError);
    RecordHistogram(EnclaveChangePinEvent::kFailed);
    return;
  }
  CHECK_EQ(model_->step(), Step::kGPMReauthForPinReset);
  rapt_ = std::move(rapt);
  model_->SetStep(Step::kGPMChangePin);
  RecordHistogram(EnclaveChangePinEvent::kReauthCompleted);
}

void ChangePinControllerImpl::OnGPMRecoverSecurityDomainClosed() {
  // User closed the reauth window.
  Reset(/*success=*/false);
  RecordHistogram(EnclaveChangePinEvent::kReauthCancelled);
}

void ChangePinControllerImpl::OnGPMPinEntered(const std::u16string& pin) {
  if (!enclave_manager_->IsRegistered()) {
    model_->SetStep(Step::kGPMError);
    RecordHistogram(EnclaveChangePinEvent::kFailed);
    return;
  }
  CHECK(rapt_.has_value() && (model_->step() == Step::kGPMChangePin ||
                              model_->step() == Step::kGPMChangeArbitraryPin));
  model_->DisableUiOrShowLoadingDialog();
  enclave_manager_->ChangePIN(
      base::UTF16ToUTF8(pin), std::move(*rapt_),
      base::BindOnce(&ChangePinControllerImpl::OnGpmPinChanged,
                     weak_ptr_factory_.GetWeakPtr()));
  rapt_.reset();
  RecordHistogram(EnclaveChangePinEvent::kNewPinEntered);
}

void ChangePinControllerImpl::OnGPMPinOptionChanged(bool is_arbitrary) {
  CHECK(model_->step() == Step::kGPMChangePin ||
        model_->step() == Step::kGPMChangeArbitraryPin);
  model_->SetStep(is_arbitrary ? Step::kGPMChangeArbitraryPin
                               : Step::kGPMChangePin);
}

// static
void ChangePinControllerImpl::RecordHistogram(EnclaveChangePinEvent event) {
  base::UmaHistogramEnumeration("WebAuthentication.Enclave.ChangePinEventsV2",
                                event);
}

void ChangePinControllerImpl::Reset(bool success) {
  if (!notify_pin_change_callback_.is_null()) {
    std::move(notify_pin_change_callback_).Run(success);
  }

  rapt_.reset();
  model_->SetStep(Step::kNotStarted);
}

void ChangePinControllerImpl::OnGpmPinChanged(bool success) {
  if (!success) {
    model_->SetStep(Step::kGPMError);
    RecordHistogram(EnclaveChangePinEvent::kFailed);
    return;
  }
  Reset(/*success=*/true);
  RecordHistogram(EnclaveChangePinEvent::kCompletedSuccessfully);
}

void ChangePinControllerImpl::NotifyPinAvailability(
    PinAvailableCallback callback) {
  std::move(callback).Run(enclave_manager_->IsRegistered() &&
                          enclave_manager_->IsReady() &&
                          enclave_manager_->has_wrapped_pin());
}

DOCUMENT_USER_DATA_KEY_IMPL(ChangePinControllerImpl);
