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

#include "components/password_manager/core/browser/password_generation_state.h"

#include <map>
#include <utility>

#include "base/callback.h"
#include "base/time/default_clock.h"
#include "components/password_manager/core/browser/form_fetcher.h"
#include "components/password_manager/core/browser/form_saver.h"
#include "components/password_manager/core/browser/password_form_manager_for_ui.h"
#include "components/password_manager/core/browser/password_manager_client.h"
#include "components/password_manager/core/browser/password_manager_driver.h"
#include "components/password_manager/core/common/password_manager_features.h"

namespace password_manager {
namespace {

using autofill::PasswordForm;
using metrics_util::GenerationPresaveConflict;
using metrics_util::LogGenerationPresaveConflict;

std::vector<PasswordForm> DeepCopyVector(
    const std::vector<const PasswordForm*>& forms) {
  std::vector<PasswordForm> result;
  result.reserve(forms.size());
  for (const PasswordForm* form : forms)
    result.emplace_back(*form);
  return result;
}

// Implementation of the UI model for "Update password?" bubble in case there is
// a conflict in generation.
class PasswordDataForUI : public PasswordFormManagerForUI {
 public:
  PasswordDataForUI(PasswordForm pending_form,
                    const std::vector<const PasswordForm*>& matches,
                    const std::vector<const PasswordForm*>& federated,
                    base::RepeatingCallback<void(bool, const PasswordForm&)>
                        bubble_interaction);
  ~PasswordDataForUI() override = default;
  PasswordDataForUI(const PasswordDataForUI&) = delete;
  PasswordDataForUI& operator=(const PasswordDataForUI&) = delete;

  // PasswordFormManagerForUI:
  const GURL& GetOrigin() const override;
  const std::map<base::string16, const PasswordForm*>& GetBestMatches()
      const override;
  std::vector<const PasswordForm*> GetFederatedMatches() const override;
  const PasswordForm& GetPendingCredentials() const override;
  metrics_util::CredentialSourceType GetCredentialSource() override;
  PasswordFormMetricsRecorder* GetMetricsRecorder() override;
  base::span<const PasswordForm* const> GetBlacklistedMatches() const override;
  base::span<const InteractionsStats> GetInteractionsStats() const override;
  bool IsBlacklisted() const override;
  void Save() override;
  void Update(const PasswordForm& credentials_to_update) override;
  void UpdateUsername(const base::string16& new_username) override;
  void UpdatePasswordValue(const base::string16& new_password) override;
  void OnNopeUpdateClicked() override;
  void OnNeverClicked() override;
  void OnNoInteraction(bool is_update) override;
  void PermanentlyBlacklist() override;
  void OnPasswordsRevealed() override;

 private:
  PasswordForm pending_form_;
  // TODO(https://crbug.com/831123): switch to vector.
  std::map<base::string16, const PasswordForm*> matches_;
  const std::vector<PasswordForm> federated_matches_;
  const std::vector<PasswordForm> non_federated_matches_;

  // Observer that waits for bubble interaction.
  // The first parameter is true iff the bubble was accepted.
  // The second parameter is the pending form.
  base::RepeatingCallback<void(bool, const PasswordForm&)>
      bubble_interaction_cb_;
};

PasswordDataForUI::PasswordDataForUI(
    PasswordForm pending_form,
    const std::vector<const PasswordForm*>& matches,
    const std::vector<const PasswordForm*>& federated,
    base::RepeatingCallback<void(bool, const PasswordForm&)> bubble_interaction)
    : pending_form_(std::move(pending_form)),
      federated_matches_(DeepCopyVector(federated)),
      non_federated_matches_(DeepCopyVector(matches)),
      bubble_interaction_cb_(std::move(bubble_interaction)) {
  for (const PasswordForm& form : non_federated_matches_)
    matches_[form.username_value] = &form;
}

const GURL& PasswordDataForUI::GetOrigin() const {
  return pending_form_.origin;
}

const std::map<base::string16, const PasswordForm*>&
PasswordDataForUI::GetBestMatches() const {
  return matches_;
}

std::vector<const PasswordForm*> PasswordDataForUI::GetFederatedMatches()
    const {
  std::vector<const PasswordForm*> result(federated_matches_.size());
  std::transform(federated_matches_.begin(), federated_matches_.end(),
                 result.begin(),
                 [](const PasswordForm& form) { return &form; });
  return result;
}

const PasswordForm& PasswordDataForUI::GetPendingCredentials() const {
  return pending_form_;
}

metrics_util::CredentialSourceType PasswordDataForUI::GetCredentialSource() {
  return metrics_util::CredentialSourceType::kPasswordManager;
}

PasswordFormMetricsRecorder* PasswordDataForUI::GetMetricsRecorder() {
  return nullptr;
}

base::span<const PasswordForm* const> PasswordDataForUI::GetBlacklistedMatches()
    const {
  return {};
}

base::span<const InteractionsStats> PasswordDataForUI::GetInteractionsStats()
    const {
  return {};
}

bool PasswordDataForUI::IsBlacklisted() const {
  // 'true' would suppress the bubble.
  return false;
}

void PasswordDataForUI::Save() {
  LogPresavedUpdateUIDismissalReason(metrics_util::CLICKED_SAVE);
  bubble_interaction_cb_.Run(true, pending_form_);
}

void PasswordDataForUI::Update(const PasswordForm&) {
  // The method is obsolete.
  NOTREACHED();
}

void PasswordDataForUI::UpdateUsername(const base::string16& new_username) {
  pending_form_.username_value = new_username;
}

void PasswordDataForUI::UpdatePasswordValue(
    const base::string16& new_password) {
  // Ignore. The generated password can be edited in-place.
}

void PasswordDataForUI::OnNopeUpdateClicked() {
  LogPresavedUpdateUIDismissalReason(metrics_util::CLICKED_CANCEL);
  bubble_interaction_cb_.Run(false, pending_form_);
}

void PasswordDataForUI::OnNeverClicked() {
  LogPresavedUpdateUIDismissalReason(metrics_util::CLICKED_NEVER);
  bubble_interaction_cb_.Run(false, pending_form_);
}

void PasswordDataForUI::OnNoInteraction(bool is_update) {
  LogPresavedUpdateUIDismissalReason(metrics_util::NO_DIRECT_INTERACTION);
  bubble_interaction_cb_.Run(false, pending_form_);
}

void PasswordDataForUI::PermanentlyBlacklist() {}

void PasswordDataForUI::OnPasswordsRevealed() {}

// Returns a form from |matches| that causes a name conflict with |generated|.
const PasswordForm* FindUsernameConflict(
    const PasswordForm& generated,
    const std::vector<const PasswordForm*>& matches) {
  for (const auto* form : matches) {
    if (form->username_value == generated.username_value)
      return form;
  }
  return nullptr;
}
}  // namespace

PasswordGenerationState::PasswordGenerationState(FormSaver* form_saver,
                                                 PasswordManagerClient* client)
    : form_saver_(form_saver),
      client_(client),
      clock_(new base::DefaultClock) {}

PasswordGenerationState::~PasswordGenerationState() = default;

std::unique_ptr<PasswordGenerationState> PasswordGenerationState::Clone(
    FormSaver* form_saver) const {
  auto clone = std::make_unique<PasswordGenerationState>(form_saver, client_);
  clone->presaved_ = presaved_;
  return clone;
}

void PasswordGenerationState::GeneratedPasswordAccepted(
    PasswordForm generated,
    const FormFetcher& fetcher,
    base::WeakPtr<PasswordManagerDriver> driver) {
  if (!base::FeatureList::IsEnabled(features::kGenerationNoOverwrites)) {
    // If the feature not enabled, just proceed with the generation.
    driver->GeneratedPasswordAccepted(generated.password_value);
    return;
  }
  // Clear the username value if there are already saved credentials with
  // the same username in order to prevent overwriting.
  std::vector<const PasswordForm*> matches = fetcher.GetNonFederatedMatches();
  if (FindUsernameConflict(generated, matches)) {
    generated.username_value.clear();
    const PasswordForm* conflict = FindUsernameConflict(generated, matches);
    if (conflict) {
      LogGenerationPresaveConflict(
          GenerationPresaveConflict::kConflictWithEmptyUsername);
      auto bubble_launcher = std::make_unique<PasswordDataForUI>(
          std::move(generated), matches, fetcher.GetFederatedMatches(),
          base::BindRepeating(&PasswordGenerationState::OnPresaveBubbleResult,
                              weak_factory_.GetWeakPtr(), std::move(driver)));
      client_->PromptUserToSaveOrUpdatePassword(std::move(bubble_launcher),
                                                true);
      return;
    } else {
      LogGenerationPresaveConflict(
          GenerationPresaveConflict::kNoConflictWithEmptyUsername);
    }
  } else {
    LogGenerationPresaveConflict(
        GenerationPresaveConflict::kNoUsernameConflict);
  }
  driver->GeneratedPasswordAccepted(generated.password_value);
}

void PasswordGenerationState::PresaveGeneratedPassword(
    PasswordForm generated,
    const std::vector<const PasswordForm*>& matches) {
  DCHECK(!generated.password_value.empty());
  // Clear the username value if there are already saved credentials with
  // the same username in order to prevent overwriting.
  if (FindUsernameConflict(generated, matches))
    generated.username_value.clear();
  generated.date_created = clock_->Now();
  if (presaved_) {
    form_saver_->UpdateReplace(generated, {} /* matches */,
                               base::string16() /* old_password */,
                               presaved_.value() /* old_primary_key */);
  } else {
    form_saver_->Save(generated, {} /* matches */,
                      base::string16() /* old_password */);
  }
  presaved_ = std::move(generated);
}

void PasswordGenerationState::PasswordNoLongerGenerated() {
  DCHECK(presaved_);
  form_saver_->Remove(*presaved_);
  presaved_.reset();
}

void PasswordGenerationState::CommitGeneratedPassword(
    PasswordForm generated,
    const std::vector<const PasswordForm*>& matches,
    const base::string16& old_password) {
  DCHECK(presaved_);
  generated.preferred = true;
  generated.date_created = clock_->Now();
  form_saver_->UpdateReplace(generated, matches, old_password,
                             presaved_.value() /* old_primary_key */);
}

void PasswordGenerationState::OnPresaveBubbleResult(
    const base::WeakPtr<PasswordManagerDriver>& driver,
    bool accepted,
    const PasswordForm& pending) {
  weak_factory_.InvalidateWeakPtrs();
  if (accepted) {
    driver->GeneratedPasswordAccepted(pending.password_value);
  }
}

}  // namespace password_manager
