// Copyright 2014 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/autofill/payments/save_card_bubble_controller_impl.h"

#include <stddef.h>

#include <string>
#include <tuple>
#include <utility>

#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
#include "chrome/browser/ui/autofill/autofill_bubble_handler.h"
#include "chrome/browser/ui/autofill/payments/save_card_ui.h"
#include "chrome/browser/ui/autofill/payments/save_payment_icon_controller.h"
#include "chrome/browser/ui/autofill/test/test_autofill_bubble_handler.h"
#include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h"
#include "chrome/browser/ui/hats/mock_trust_safety_sentiment_service.h"
#include "chrome/browser/ui/hats/trust_safety_sentiment_service_factory.h"
#include "chrome/browser/ui/tabs/tab_activity_simulator.h"
#include "chrome/browser/ui/tabs/tab_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/autofill/core/browser/data_manager/personal_data_manager.h"
#include "components/autofill/core/browser/data_manager/test_personal_data_manager.h"
#include "components/autofill/core/browser/data_model/payments/credit_card.h"
#include "components/autofill/core/browser/metrics/autofill_metrics.h"
#include "components/autofill/core/browser/metrics/payments/credit_card_save_metrics.h"
#include "components/autofill/core/browser/metrics/payments/credit_card_save_metrics_desktop.h"
#include "components/autofill/core/browser/metrics/payments/manage_cards_prompt_metrics.h"
#include "components/autofill/core/browser/payments/payments_autofill_client.h"
#include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/strings/grit/components_strings.h"
#include "components/tabs/public/mock_tab_interface.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/test/mock_navigation_handle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/unowned_user_data/scoped_unowned_user_data.h"

using base::Bucket;
using testing::ElementsAre;

namespace autofill {
namespace {

using CardSaveType = payments::PaymentsAutofillClient::CardSaveType;
using SaveCardPromptOffer = autofill_metrics::SaveCardPromptOffer;
using SaveCardPromptResultDesktop =
    autofill_metrics::SaveCardPromptResultDesktop;
using SaveCreditCardOptions =
    payments::PaymentsAutofillClient::SaveCreditCardOptions;

constexpr std::string_view kSaveCardPromptResultDesktopBaseHistogram =
    "Autofill.SaveCreditCardPromptResult.Desktop";

std::unique_ptr<KeyedService> BuildTestPersonalDataManager(
    content::BrowserContext* context) {
  auto personal_data_manager = std::make_unique<TestPersonalDataManager>();
  personal_data_manager->test_payments_data_manager()
      .SetAutofillPaymentMethodsEnabled(true);
  return personal_data_manager;
}

// Test AutofillBubbleBase implementation which:
// - Notifies the controller when the bubble hides (to match prod).
// - Tracks the bubble's visibility.
class ObserveHideTestAutofillBubble : public AutofillBubbleBase {
 public:
  explicit ObserveHideTestAutofillBubble(content::WebContents* web_contents)
      : web_contents_(web_contents->GetWeakPtr()) {}
  ~ObserveHideTestAutofillBubble() override = default;

  void Show() { is_visible_ = true; }

  void Hide() override {
    // Call OnBubbleClosed() because the real implementation does so.
    if (web_contents_) {
      auto* controller = static_cast<SaveCardBubbleControllerImpl*>(
          SaveCardBubbleControllerImpl::FromWebContents(web_contents_.get()));
      controller->OnBubbleClosed(PaymentsUiClosedReason::kUnknown);
    }

    is_visible_ = false;
  }

  bool IsMouseHovered() const override { return false; }

  bool is_visible() { return is_visible_; }

 private:
  // WeakPtr because ObserveHideTestAutofillBubble outlives the WebContents in
  // tests.
  base::WeakPtr<content::WebContents> web_contents_;

  bool is_visible_;
};

// TestAutofillBubbleHandler which provides access to bubbles it creates.
class ExposeBubbleAutofillBubbleHandler : public TestAutofillBubbleHandler {
 public:
  ExposeBubbleAutofillBubbleHandler() = default;
  ExposeBubbleAutofillBubbleHandler(const ExposeBubbleAutofillBubbleHandler&) =
      delete;
  ExposeBubbleAutofillBubbleHandler& operator=(
      const ExposeBubbleAutofillBubbleHandler&) = delete;
  ~ExposeBubbleAutofillBubbleHandler() override = default;

  AutofillBubbleBase* ShowSaveCreditCardBubble(
      content::WebContents* web_contents,
      SaveCardBubbleController* controller,
      bool is_use_gesture) override {
    if (!save_card_bubble_) {
      save_card_bubble_ =
          std::make_unique<ObserveHideTestAutofillBubble>(web_contents);
    }
    save_card_bubble_->Show();
    return save_card_bubble_.get();
  }

  AutofillBubbleBase* ShowSaveCardConfirmationBubble(
      content::WebContents* web_contents,
      SaveCardBubbleController* controller) override {
    if (!confirmation_bubble_) {
      confirmation_bubble_ =
          std::make_unique<ObserveHideTestAutofillBubble>(web_contents);
    }
    confirmation_bubble_->Show();
    return confirmation_bubble_.get();
  }

  bool is_save_card_bubble_visible() {
    return save_card_bubble_ && save_card_bubble_->is_visible();
  }

  bool is_confirmation_bubble_visible() {
    return confirmation_bubble_ && confirmation_bubble_->is_visible();
  }

 private:
  std::unique_ptr<ObserveHideTestAutofillBubble> save_card_bubble_;
  std::unique_ptr<ObserveHideTestAutofillBubble> confirmation_bubble_;
};

class TestWebContentsDelegate : public content::WebContentsDelegate {
 public:
  explicit TestWebContentsDelegate(BrowserWindowInterface* browser_window)
      : browser_window_(browser_window) {}

  content::WebContents* OpenURLFromTab(
      content::WebContents* source,
      const content::OpenURLParams& params,
      base::OnceCallback<void(content::NavigationHandle&)>
          navigation_handle_callback) override {
    browser_window_->OpenGURL(params.url, params.disposition);
    return nullptr;
  }

 private:
  raw_ptr<BrowserWindowInterface> browser_window_;
};

class TestSaveCardBubbleControllerImpl : public SaveCardBubbleControllerImpl {
 public:
  static void CreateForTesting(content::WebContents* web_contents) {
    web_contents->SetUserData(
        UserDataKey(),
        std::make_unique<TestSaveCardBubbleControllerImpl>(web_contents));
  }

  // Overriding because parent function requires a browser window to redirect
  // properly, which is not available in unit tests.
  void ShowPaymentsSettingsPage() override {}

  explicit TestSaveCardBubbleControllerImpl(content::WebContents* web_contents)
      : SaveCardBubbleControllerImpl(web_contents) {}

  void SimulateNavigation() {
    content::MockNavigationHandle handle;
    handle.set_has_committed(true);
    DidFinishNavigation(&handle);
  }
 protected:
  bool IsPaymentsSyncTransportEnabledWithoutSyncFeature() const override {
    return false;
  }
};

class SaveCardBubbleControllerImplTest
    : public ChromeRenderViewHostTestHarness {
 public:
  SaveCardBubbleControllerImplTest()
      : ChromeRenderViewHostTestHarness(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  SaveCardBubbleControllerImplTest(SaveCardBubbleControllerImplTest&) = delete;
  SaveCardBubbleControllerImplTest& operator=(
      SaveCardBubbleControllerImplTest&) = delete;

  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();

    web_contents_delegate_ =
        std::make_unique<TestWebContentsDelegate>(&mock_browser_window_);

    // Configure mock browser window.
    ON_CALL(mock_browser_window_, GetUnownedUserDataHost())
        .WillByDefault(testing::ReturnRef(browser_unowned_user_data_host_));
    ON_CALL(mock_browser_window_, GetProfile())
        .WillByDefault(testing::Return(profile()));
    ON_CALL(mock_browser_window_, GetTabStripModel())
        .WillByDefault(testing::Return(tab_strip_model_.get()));
    ON_CALL(mock_browser_window_, OpenGURL(testing::_, testing::_))
        .WillByDefault(
            [this](const GURL& url, WindowOpenDisposition disposition) {
              if (disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB) {
                content::WebContents* new_contents = AddTab(url);
                int new_index =
                    tab_strip_model_->GetIndexOfWebContents(new_contents);
                tab_activity_simulator_.SwitchToTabAt(tab_strip_model_.get(),
                                                      new_index);
              }
            });

    // Set up tab strip model.
    tab_strip_model_delegate_.SetBrowserWindowInterface(&mock_browser_window_);
    tab_strip_model_ =
        std::make_unique<TabStripModel>(&tab_strip_model_delegate_, profile());

    // Create the initial active tab.
    AddTab(GURL("about:blank"));

    // Attach test bubble handler to browser window host.
    scoped_autofill_bubble_handler_ =
        std::make_unique<ui::ScopedUnownedUserData<AutofillBubbleHandler>>(
            browser_unowned_user_data_host_, test_autofill_bubble_handler_);

    // Initialize a tracker for TrustSafetySentimentService to work properly.
    metrics::DesktopSessionDurationTracker::Initialize();
    mock_sentiment_service_ = static_cast<MockTrustSafetySentimentService*>(
        TrustSafetySentimentServiceFactory::GetInstance()
            ->SetTestingFactoryAndUse(
                profile(),
                base::BindRepeating(&BuildMockTrustSafetySentimentService)));

    // Set the visibility to VISIBLE.
    SimulateTabVisibilityChange(content::Visibility::VISIBLE);
  }

  void TearDown() override {
    mock_sentiment_service_ = nullptr;
    did_on_confirmation_closed_callback_run_ = false;
    personal_data_manager()->test_payments_data_manager().ClearCreditCards();
    scoped_autofill_bubble_handler_.reset();
    tab_strip_model_.reset();
    tab_strip_model_delegate_.SetBrowserWindowInterface(nullptr);
    ChromeRenderViewHostTestHarness::TearDown();
    metrics::DesktopSessionDurationTracker::CleanupForTesting();
  }

  ExposeBubbleAutofillBubbleHandler* GetAutofillBubbleHandler() {
    return &test_autofill_bubble_handler_;
  }

  bool IsSaveCardBubbleVisible() {
    return GetAutofillBubbleHandler()->is_save_card_bubble_visible();
  }

  bool IsConfirmationBubbleVisible() {
    return GetAutofillBubbleHandler()->is_confirmation_bubble_visible();
  }

  void SetLegalMessage(const std::string& message_json,
                       SaveCreditCardOptions options =
                           SaveCreditCardOptions().with_show_prompt()) {
    std::optional<base::Value> value(base::JSONReader::Read(
        message_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS));
    ASSERT_TRUE(value);
    ASSERT_TRUE(value->is_dict());
    LegalMessageLines legal_message_lines;
    LegalMessageLine::Parse(value->GetDict(), &legal_message_lines,
                            /*escape_apostrophes=*/true);
    controller()->OfferUploadSave(CreditCard(), legal_message_lines, options,
                                  base::BindOnce(&UploadSaveCardCallback));
  }

  void ShowLocalBubble(const CreditCard* card = nullptr,
                       SaveCreditCardOptions options =
                           SaveCreditCardOptions().with_show_prompt()) {
    controller()->OfferLocalSave(
        card ? CreditCard(*card)
             : autofill::test::GetCreditCard(),  // Visa by default
        options, base::BindOnce(&LocalSaveCardCallback));
  }

  void ShowUploadBubble(SaveCreditCardOptions options =
                            SaveCreditCardOptions().with_show_prompt()) {
    if (options.card_save_type == CardSaveType::kCvcSaveOnly) {
      SetLegalMessage("{}", options);
      return;
    }
    SetLegalMessage(
        "{"
        "  \"line\" : [ {"
        "     \"template\": \"This is the entire message.\""
        "  } ]"
        "}",
        options);
  }

  void ShowConfirmationBubbleView(bool card_saved) {
    controller()->ShowConfirmationBubbleView(
        /*card_saved=*/card_saved,
        /*is_for_save_and_fill=*/false,
        /*on_confirmation_closed_callback=*/
        base::BindOnce(
            &SaveCardBubbleControllerImplTest::OnConfirmationClosedCallback,
            weak_ptr_factory_.GetWeakPtr()));
  }

  void CloseBubble(PaymentsUiClosedReason closed_reason =
                       PaymentsUiClosedReason::kNotInteracted) {
    controller()->OnBubbleClosed(closed_reason);
  }

  void CloseAndReshowBubble() {
    CloseBubble();
    controller()->ReshowBubble(/*is_user_gesture=*/true);
  }

  void ClickSaveButton(bool is_upload = false) {
    controller()->OnSaveButton({});
    PaymentsUiClosedReason close_reason = PaymentsUiClosedReason::kAccepted;
    if (is_upload) {
      // The `Upload` dialog shows loading and doesn't close by itself. The
      // dialog needs to be closed and the reason recorded for closure is
      // `kClosed`.
      close_reason = PaymentsUiClosedReason::kClosed;
      CloseBubble(close_reason);
    }
    controller()->OnBubbleClosed(close_reason);
    if (controller()->ShouldShowPaymentSavedLabelAnimation()) {
      controller()->OnAnimationEnded();
    }
  }

  void AddCreditCard(const CreditCard& card) {
    personal_data_manager()->test_payments_data_manager().AddCreditCard(card);
  }

 protected:
  void SimulateTabVisibilityChange(content::Visibility visibility) {
    active_web_contents()->UpdateWebContentsVisibility(visibility);

    // When BubbleManager is enabled, the framework destroys the widget on tab
    // hide. Because a fake bubble is used that the framework doesn't track, it
    // must manually simulate this destruction lifecycle.
    if (visibility == content::Visibility::HIDDEN) {
      controller()->HideSaveCardBubble();
    } else if (visibility == content::Visibility::VISIBLE) {
      if (controller()->ShouldReshowOnTabVisible()) {
        controller()->ReshowBubble(/*is_user_gesture=*/false);
      }
    }
  }

  TestSaveCardBubbleControllerImpl* controller() {
    return static_cast<TestSaveCardBubbleControllerImpl*>(
        TestSaveCardBubbleControllerImpl::FromWebContents(
            active_web_contents()));
  }

  content::WebContents* active_web_contents() {
    return tab_strip_model_->GetActiveWebContents();
  }

  TestPersonalDataManager* personal_data_manager() {
    return static_cast<TestPersonalDataManager*>(
        PersonalDataManagerFactory::GetForBrowserContext(profile()));
  }

  tabs::TabInterface* active_tab() {
    return tab_strip_model_->GetTabForWebContents(active_web_contents());
  }

  content::WebContents* AddTab(const GURL& url) {
    content::WebContents* new_contents =
        tab_activity_simulator_.AddWebContentsAndNavigate(
            tab_strip_model_.get(), url);
    new_contents->SetDelegate(web_contents_delegate_.get());
    TestSaveCardBubbleControllerImpl::CreateForTesting(new_contents);
    return new_contents;
  }

  // ChromeRenderViewHostTestHarness:
  TestingProfile::TestingFactories GetTestingFactories() const override {
    return TestingProfile::TestingFactories({TestingProfile::TestingFactory(
        PersonalDataManagerFactory::GetInstance(),
        base::BindRepeating(&BuildTestPersonalDataManager))});
  }

  raw_ptr<MockTrustSafetySentimentService> mock_sentiment_service_ = nullptr;
  bool did_on_confirmation_closed_callback_run_ = false;

  testing::NiceMock<MockBrowserWindowInterface> mock_browser_window_;
  ui::UnownedUserDataHost browser_unowned_user_data_host_;

  TestTabStripModelDelegate tab_strip_model_delegate_;
  std::unique_ptr<TabStripModel> tab_strip_model_;
  TabActivitySimulator tab_activity_simulator_;
  const tabs::TabModel::PreventFeatureInitializationForTesting
      prevent_features_;

  std::unique_ptr<TestWebContentsDelegate> web_contents_delegate_;

 private:
  static void UploadSaveCardCallback(
      payments::PaymentsAutofillClient::SaveCardOfferUserDecision user_decision,
      const payments::PaymentsAutofillClient::UserProvidedCardDetails&
          user_provided_card_details) {}
  static void LocalSaveCardCallback(
      payments::PaymentsAutofillClient::SaveCardOfferUserDecision
          user_decision) {}
  void OnConfirmationClosedCallback() {
    did_on_confirmation_closed_callback_run_ = true;
  }

  ExposeBubbleAutofillBubbleHandler test_autofill_bubble_handler_;
  std::unique_ptr<ui::ScopedUnownedUserData<AutofillBubbleHandler>>
      scoped_autofill_bubble_handler_;
  base::WeakPtrFactory<SaveCardBubbleControllerImplTest> weak_ptr_factory_{
      this};
};

// Tests that the legal message lines vector is empty when doing a local save so
// that no legal messages will be shown to the user in that case.
TEST_F(SaveCardBubbleControllerImplTest, LegalMessageLinesEmptyOnLocalSave) {
  ShowUploadBubble();
  CloseBubble();
  ShowLocalBubble();
  EXPECT_TRUE(controller()->GetLegalMessageLines().empty());
}

TEST_F(SaveCardBubbleControllerImplTest,
       PropagateShouldRequestNameFromUserWhenFalse) {
  ShowUploadBubble();
  EXPECT_FALSE(controller()->ShouldRequestNameFromUser());
}

TEST_F(SaveCardBubbleControllerImplTest,
       PropagateShouldRequestNameFromUserWhenTrue) {
  ShowUploadBubble(SaveCreditCardOptions()
                       .with_should_request_name_from_user(true)
                       .with_show_prompt());
  EXPECT_TRUE(controller()->ShouldRequestNameFromUser());
}

TEST_F(SaveCardBubbleControllerImplTest,
       PropagateShouldRequestExpirationDateFromUserWhenFalse) {
  ShowUploadBubble(SaveCreditCardOptions()
                       .with_should_request_name_from_user(true)
                       .with_show_prompt());

  EXPECT_FALSE(controller()->ShouldRequestExpirationDateFromUser());
}

TEST_F(SaveCardBubbleControllerImplTest,
       PropagateShouldRequestExpirationDateFromUserWhenTrue) {
  ShowUploadBubble(SaveCreditCardOptions()
                       .with_should_request_name_from_user(true)
                       .with_should_request_expiration_date_from_user(true)
                       .with_show_prompt());

  EXPECT_TRUE(controller()->ShouldRequestExpirationDateFromUser());
}

using SaveCreditCardPromptResultMetricTestData =
    std::tuple<PaymentsUiClosedReason,
               autofill_metrics::LegacySaveCardPromptResult>;

// Test fixture to ensure the correct reporting of UMA metric
// Autofill.SaveCreditCardPromptResult{SaveDestination}.{UserGroup}.
class SaveCreditCardPromptResultMetricTest
    : public SaveCardBubbleControllerImplTest,
      public testing::WithParamInterface<
          SaveCreditCardPromptResultMetricTestData> {
 public:
  SaveCreditCardPromptResultMetricTest()
      : closed_reason_(std::get<0>(GetParam())),
        prompt_result_(std::get<1>(GetParam())) {}
  ~SaveCreditCardPromptResultMetricTest() override = default;

 protected:
  const PaymentsUiClosedReason closed_reason_;
  const autofill_metrics::LegacySaveCardPromptResult prompt_result_;
};

INSTANTIATE_TEST_SUITE_P(
    ,
    SaveCreditCardPromptResultMetricTest,
    testing::Values(
        SaveCreditCardPromptResultMetricTestData(
            PaymentsUiClosedReason::kAccepted,
            autofill_metrics::LegacySaveCardPromptResult::kAccepted),
        SaveCreditCardPromptResultMetricTestData(
            PaymentsUiClosedReason::kCancelled,
            autofill_metrics::LegacySaveCardPromptResult::kCancelled),
        SaveCreditCardPromptResultMetricTestData(
            PaymentsUiClosedReason::kClosed,
            autofill_metrics::LegacySaveCardPromptResult::kClosed),
        SaveCreditCardPromptResultMetricTestData(
            PaymentsUiClosedReason::kNotInteracted,
            autofill_metrics::LegacySaveCardPromptResult::kNotInteracted),
        SaveCreditCardPromptResultMetricTestData(
            PaymentsUiClosedReason::kLostFocus,
            autofill_metrics::LegacySaveCardPromptResult::kLostFocus)));

// Tests that after the user interacts with a "save *local* card" dialog and
// *does not* have any card data on file, metrics
// Autofill.SaveCreditCardPromptResult.Local.Aggregate and .UserHasNoCards are
// recorded.
TEST_P(SaveCreditCardPromptResultMetricTest,
       EmitsSavePromptResultLocalHasNoCards) {
  personal_data_manager()->test_payments_data_manager().ClearCreditCards();
  base::HistogramTester histogram_tester;
  ShowLocalBubble(
      /*card=*/nullptr,
      /*options=*/SaveCreditCardOptions().with_show_prompt(true));
  if (closed_reason_ == PaymentsUiClosedReason::kAccepted) {
    controller()->OnSaveButton({});
  }
  CloseBubble(closed_reason_);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Local.Aggregate", prompt_result_, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Local.UserHasNoCards",
      prompt_result_, 1);
}

// Tests that after the user interacts with a "save *server* card" dialog and
// *does not* have any card data on file, metrics
// Autofill.SaveCreditCardPromptResult.Upload.Aggregate and .UserHasNoCards are
// recorded.
TEST_P(SaveCreditCardPromptResultMetricTest,
       EmitsSavePromptResultUploadHasNoCards) {
  personal_data_manager()->test_payments_data_manager().ClearCreditCards();
  base::HistogramTester histogram_tester;
  ShowUploadBubble(SaveCreditCardOptions().with_show_prompt(true));
  if (closed_reason_ == PaymentsUiClosedReason::kAccepted) {
    controller()->OnSaveButton({});
    // On Save button clicked, the dialog shows loading and doesn't close by
    // itself. The dialog needs to be closed and the reason recorded for
    // closure is `kClosed`.
    CloseBubble(PaymentsUiClosedReason::kClosed);
  } else {
    CloseBubble(closed_reason_);
  }

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Upload.Aggregate", prompt_result_,
      1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Upload.UserHasNoCards",
      prompt_result_, 1);
}

// Tests that after the user interacts with a "save *local* card" dialog and
// *does* have card data on file, metrics
// Autofill.SaveCreditCardPromptResult.Local.Aggregate and .UserHasSavedCards
// are recorded.
TEST_P(SaveCreditCardPromptResultMetricTest,
       EmitsSavePromptResultLocalHasSavedCards) {
  personal_data_manager()->test_payments_data_manager().ClearCreditCards();
  AddCreditCard(test::GetCreditCard());
  base::HistogramTester histogram_tester;
  ShowLocalBubble(
      /*card=*/nullptr,
      /*options=*/SaveCreditCardOptions().with_show_prompt(true));
  if (closed_reason_ == PaymentsUiClosedReason::kAccepted) {
    controller()->OnSaveButton({});
  }
  CloseBubble(closed_reason_);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Local.Aggregate", prompt_result_, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Local.UserHasSavedCards",
      prompt_result_, 1);
}

// Tests that after the user interacts with a "save *server* card" dialog and
// *does* have card data on file, metrics
// Autofill.SaveCreditCardPromptResult.Upload.Aggregate and .UserHasSavedCards
// are recorded.
TEST_P(SaveCreditCardPromptResultMetricTest,
       EmitsSavePromptResultUploadHasSavedCards) {
  personal_data_manager()->test_payments_data_manager().ClearCreditCards();
  AddCreditCard(test::GetCreditCard());
  base::HistogramTester histogram_tester;
  ShowUploadBubble(SaveCreditCardOptions().with_show_prompt(true));
  if (closed_reason_ == PaymentsUiClosedReason::kAccepted) {
    // On Save button clicked, the dialog shows loading and doesn't close by
    // itself.
    controller()->OnSaveButton({});
  } else {
    CloseBubble(closed_reason_);
  }

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Upload.Aggregate", prompt_result_,
      1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Upload.UserHasSavedCards",
      prompt_result_, 1);
}

// Param of the SaveCardBubbleSingletonTestData:
// -- bool first_shown_is_local;
// -- bool second_and_third_shown_are_local;
typedef std::tuple<bool, bool> SaveCardBubbleSingletonTestData;

// One test case will be run several times till we cover all the param
// combinations of the |SaveCardBubbleSingletonTestData|. GetParam() will help
// get the specific param value for a particular run.
class SaveCardBubbleSingletonTest
    : public SaveCardBubbleControllerImplTest,
      public testing::WithParamInterface<SaveCardBubbleSingletonTestData> {
 public:
  SaveCardBubbleSingletonTest()
      : first_shown_is_local_(std::get<0>(GetParam())),
        second_and_third_shown_are_local_(std::get<1>(GetParam())) {}

  ~SaveCardBubbleSingletonTest() override = default;

  void ShowBubble(bool is_local) {
    is_local ? ShowLocalBubble() : ShowUploadBubble();
  }

  void TriggerFlow() {
    ShowBubble(first_shown_is_local_);
    ShowBubble(second_and_third_shown_are_local_);
    ShowBubble(second_and_third_shown_are_local_);
  }

  const bool first_shown_is_local_;
  const bool second_and_third_shown_are_local_;
};

INSTANTIATE_TEST_SUITE_P(,
                         SaveCardBubbleSingletonTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

TEST_P(SaveCardBubbleSingletonTest, OnlyOneActiveBubble) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  std::string suffix =
      first_shown_is_local_ ? ".Local.FirstShow" : ".Upload.FirstShow";

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptOffer" + suffix,
      SaveCardPromptOffer::kShown, 1);
}

// Note that even though in prod the four options in the SaveCreditCardOptions
// struct can be true at the same time, we don't support that in the test case
// (by the way we create histogram name suffixes).
struct SaveCardOptionParam {
  bool should_request_name_from_user;
  bool should_request_expiration_date_from_user;
  bool has_multiple_legal_lines;
  bool has_same_last_four_as_server_card_but_different_expiration_date;
  CardSaveType card_save_type;
};

const SaveCardOptionParam kSaveCardOptionParam[] = {
    {false, false, false, false, CardSaveType::kCardSaveOnly},
    {true, false, false, false, CardSaveType::kCardSaveOnly},
    {false, true, false, false, CardSaveType::kCardSaveOnly},
    {false, false, true, false, CardSaveType::kCardSaveOnly},
    {false, false, false, true, CardSaveType::kCardSaveOnly},
    {false, false, false, false, CardSaveType::kCardSaveWithCvc}};

// Param of the SaveCardBubbleSingletonTestData:
// -- std::string save_destination
// -- std::string show_type
// -- SaveCardOptionParam save_card_option_param
typedef std::tuple<std::string, std::string, SaveCardOptionParam>
    SaveCardBubbleLoggingTestData;

// Test class to ensure the save card bubble events are logged correctly.
class SaveCardBubbleLoggingTest
    : public SaveCardBubbleControllerImplTest,
      public ::testing::WithParamInterface<SaveCardBubbleLoggingTestData> {
 public:
  SaveCardBubbleLoggingTest()
      : save_destination_(std::get<0>(GetParam())),
        show_type_(std::get<1>(GetParam())) {
    SaveCardOptionParam save_card_option_param = std::get<2>(GetParam());
    save_credit_card_options_ =
        SaveCreditCardOptions()
            .with_should_request_name_from_user(
                save_card_option_param.should_request_name_from_user)
            .with_should_request_expiration_date_from_user(
                save_card_option_param.should_request_expiration_date_from_user)
            .with_has_multiple_legal_lines(
                save_card_option_param.has_multiple_legal_lines)
            .with_same_last_four_as_server_card_but_different_expiration_date(
                save_card_option_param
                    .has_same_last_four_as_server_card_but_different_expiration_date)
            .with_card_save_type(save_card_option_param.card_save_type);
  }

  ~SaveCardBubbleLoggingTest() override = default;

  void SetUp() override {
    SaveCardBubbleControllerImplTest::SetUp();

    if (save_destination_ == "Local" &&
        (GetSaveCreditCardOptions().has_multiple_legal_lines ||
         GetSaveCreditCardOptions()
             .has_same_last_four_as_server_card_but_different_expiration_date)) {
      GTEST_SKIP()
          << "Not applicable for local save, as legal lines or the "
             "condition (same last four digits, different "
             "expiration date) is only possible for server save scenarios.";
    }
  }

  void TriggerFlow(bool show_prompt = true) {
    if (save_destination_ == "Local") {
      if (show_type_ == "FirstShow") {
        ShowLocalBubble(/*card=*/nullptr,
                        /*options=*/GetSaveCreditCardOptions().with_show_prompt(
                            show_prompt));
      } else {
        ASSERT_EQ(show_type_, "Reshows");
        ShowLocalBubble(/*card=*/nullptr,
                        /*options=*/GetSaveCreditCardOptions().with_show_prompt(
                            show_prompt));
        CloseAndReshowBubble();
      }
    } else {
      ASSERT_EQ(save_destination_, "Upload");
      if (show_type_ == "FirstShow") {
        ShowUploadBubble(
            GetSaveCreditCardOptions().with_show_prompt(show_prompt));
      } else {
        ASSERT_EQ(show_type_, "Reshows");
        ShowUploadBubble(
            GetSaveCreditCardOptions().with_show_prompt(show_prompt));
        CloseAndReshowBubble();
      }
    }
  }

  SaveCreditCardOptions GetSaveCreditCardOptions() {
    return save_credit_card_options_;
  }

  std::string GetHistogramNameSuffix() {
    std::string result = "." + save_destination_ + "." + show_type_;

    if (GetSaveCreditCardOptions().should_request_name_from_user) {
      result += ".RequestingCardholderName";
    }

    if (GetSaveCreditCardOptions().should_request_expiration_date_from_user) {
      result += ".RequestingExpirationDate";
    }

    if (GetSaveCreditCardOptions().has_multiple_legal_lines) {
      result += ".WithMultipleLegalLines";
    }

    if (GetSaveCreditCardOptions().legal_lines_mention_personalization) {
      result += ".LegalMessageLinesMentionPersonalization";
    }

    if (GetSaveCreditCardOptions()
            .has_same_last_four_as_server_card_but_different_expiration_date) {
      result += ".WithSameLastFourButDifferentExpiration";
    }
    if (GetSaveCreditCardOptions().card_save_type ==
        CardSaveType::kCardSaveWithCvc) {
      result += ".SavingWithCvc";
    }

    return result;
  }

  const std::string save_destination_;
  const std::string show_type_;

 private:
  SaveCreditCardOptions save_credit_card_options_;
};

INSTANTIATE_TEST_SUITE_P(
    ,
    SaveCardBubbleLoggingTest,
    testing::Combine(testing::Values("Local", "Upload"),
                     testing::Values("FirstShow", "Reshows"),
                     testing::ValuesIn(kSaveCardOptionParam)));

TEST_P(SaveCardBubbleLoggingTest, Metrics_ShowBubble) {
  base::HistogramTester histogram_tester;
  TriggerFlow();

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptOffer" + GetHistogramNameSuffix(),
      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCardBubbleLoggingTest, Metrics_ShowIconOnly) {
  // This case does not happen when it is a reshow.
  if (show_type_ == "Reshows") {
    return;
  }

  base::HistogramTester histogram_tester;
  TriggerFlow(/*show_prompt=*/false);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptOffer" + GetHistogramNameSuffix(),
      SaveCardPromptOffer::kNotShownMaxStrikesReached, 1);
}

// TODO(https://crbug.com/448030345): Flaky on Linux ASan
#if BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER)
#define MAYBE_Metrics_SaveButton DISABLED_Metrics_SaveButton
#else
#define MAYBE_Metrics_SaveButton Metrics_SaveButton
#endif
TEST_P(SaveCardBubbleLoggingTest, MAYBE_Metrics_SaveButton) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  controller()->OnSaveButton({});
  // On Save button clicked, the `Upload` dialog shows loading and doesn't close
  // by itself. The `Upload` dialog needs to be closed and the reason recorded
  // for closure is `kClosed`.
  PaymentsUiClosedReason closed_reason =
      save_destination_ == "Upload" ? PaymentsUiClosedReason::kClosed
                                    : PaymentsUiClosedReason::kAccepted;
  CloseBubble(closed_reason);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult" + GetHistogramNameSuffix(),
      autofill_metrics::LegacySaveCardPromptResult::kAccepted, 1);
}

// TODO(https://crbug.com/448030345): Flaky on Linux ASan
#if BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER)
#define MAYBE_Metrics_CancelButton DISABLED_Metrics_CancelButton
#else
#define MAYBE_Metrics_CancelButton Metrics_CancleButton
#endif
TEST_P(SaveCardBubbleLoggingTest, MAYBE_Metrics_CancelButton) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kCancelled);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult" + GetHistogramNameSuffix(),
      autofill_metrics::LegacySaveCardPromptResult::kCancelled, 1);
}

// TODO(https://crbug.com/448030345): Flaky on Linux ASan
#if BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER)
#define MAYBE_Metrics_Closed DISABLED_Metrics_Closed
#else
#define MAYBE_Metrics_Closed Metrics_Closed
#endif
TEST_P(SaveCardBubbleLoggingTest, MAYBE_Metrics_Closed) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult" + GetHistogramNameSuffix(),
      autofill_metrics::LegacySaveCardPromptResult::kClosed, 1);
}

TEST_P(SaveCardBubbleLoggingTest, Metrics_NotInteracted) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kNotInteracted);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult" + GetHistogramNameSuffix(),
      autofill_metrics::LegacySaveCardPromptResult::kNotInteracted, 1);
}

TEST_P(SaveCardBubbleLoggingTest, Metrics_LostFocus) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kLostFocus);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult" + GetHistogramNameSuffix(),
      autofill_metrics::LegacySaveCardPromptResult::kLostFocus, 1);
}

TEST_P(SaveCardBubbleLoggingTest, Metrics_Unknown) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kUnknown);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult" + GetHistogramNameSuffix(),
      autofill_metrics::LegacySaveCardPromptResult::kUnknown, 1);
}

TEST_P(SaveCardBubbleLoggingTest, Metrics_LegalMessageLinkedClicked) {
  if (save_destination_ == "Local") {
    return;
  }

  TriggerFlow();
  base::HistogramTester histogram_tester;
  base::UserActionTester user_action_tester;
  controller()->OnLegalMessageLinkClicked(GURL("http://www.example.com"));

  EXPECT_EQ(1, user_action_tester.GetActionCount(
                   "Autofill_CreditCardUpload_LegalMessageLinkClicked"));
}

// Test class to ensure that correct metric is logged when the save card bubble
// is shown or not shown.
class SaveCreditCardPromptOfferMetricTest
    : public SaveCardBubbleControllerImplTest,
      public ::testing::WithParamInterface</*is_upload_save*/ bool> {
 public:
  void TriggerFlow(bool show_prompt, SaveCreditCardOptions options = {}) {
    if (IsUploadSave()) {
      ShowUploadBubble(options.with_show_prompt(show_prompt));
    } else {
      ShowLocalBubble(/*card=*/nullptr, options.with_show_prompt(show_prompt));
    }
  }

  std::string GetBaseHistogramName() {
    return base::StrCat({"Autofill.SaveCreditCardPromptOffer.Desktop",
                         IsUploadSave() ? ".Server" : ".Local"});
  }

  bool IsUploadSave() { return GetParam(); }
};

INSTANTIATE_TEST_SUITE_P(,
                         SaveCreditCardPromptOfferMetricTest,
                         testing::Bool());

TEST_P(SaveCreditCardPromptOfferMetricTest, LogsBubbleShown) {
  base::HistogramTester histogram_tester;
  TriggerFlow(/*show_prompt=*/true, SaveCreditCardOptions().with_card_save_type(
                                        CardSaveType::kCardSaveOnly));

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCreditCardPromptOfferMetricTest,
       LogsBubbleShown_WhenRequestingCardHolderName) {
  base::HistogramTester histogram_tester;
  TriggerFlow(
      /*show_prompt=*/true,
      SaveCreditCardOptions()
          .with_should_request_name_from_user(true)
          .with_card_save_type(CardSaveType::kCardSaveOnly));

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".RequestingCardholderName"}),
      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCreditCardPromptOfferMetricTest,
       LogsBubbleShown_WhenRequestingExpirationDate) {
  base::HistogramTester histogram_tester;
  TriggerFlow(
      /*show_prompt=*/true,
      SaveCreditCardOptions()
          .with_should_request_expiration_date_from_user(true)
          .with_card_save_type(CardSaveType::kCardSaveOnly));

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".RequestingExpirationDate"}),
      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCreditCardPromptOfferMetricTest, LogsBubbleShown_WhenSavingWithCvc) {
  base::HistogramTester histogram_tester;
  TriggerFlow(
      /*show_prompt=*/true, SaveCreditCardOptions().with_card_save_type(
                                CardSaveType::kCardSaveWithCvc));

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".SavingWithCvc"}),
      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCreditCardPromptOfferMetricTest,
       LogsBubbleShown_ForPromptWithMultipleLegalLines) {
  if (!IsUploadSave()) {
    GTEST_SKIP() << "Not applicable for local save, as legal lines are "
                    "present only in server save scenarios";
  }

  base::HistogramTester histogram_tester;
  TriggerFlow(
      /*show_prompt=*/true,
      SaveCreditCardOptions()
          .with_has_multiple_legal_lines(true)
          .with_card_save_type(CardSaveType::kCardSaveOnly));

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".WithMultipleLegalLines"}),
      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCreditCardPromptOfferMetricTest,
       LogsBubbleShown_ForPromptWithLegalLinesMentioningPersonalization) {
  if (!IsUploadSave()) {
    GTEST_SKIP() << "Not applicable for local save, as legal lines are "
                    "present only in server save scenarios";
  }
  base::test::ScopedFeatureList feature_list{
      features::kAutofillParseLegalMessageLines};

  base::HistogramTester histogram_tester;
  TriggerFlow(
      /*show_prompt=*/true,
      SaveCreditCardOptions()
          .with_legal_lines_mention_personalization(true)
          .with_card_save_type(CardSaveType::kCardSaveOnly));

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat(
          {GetBaseHistogramName(), ".LegalMessageLinesMentionPersonalization"}),
      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCreditCardPromptOfferMetricTest,
       LogsBubbleShown_ForCardWithSameLastFourButDifferentExpiration) {
  if (!IsUploadSave()) {
    GTEST_SKIP() << "Not applicable for local save, as the condition (same "
                    "last four digits, different expiration date) is only "
                    "possible for server save scenarios.";
  }

  base::HistogramTester histogram_tester;
  TriggerFlow(
      /*show_prompt=*/true,
      SaveCreditCardOptions()
          .with_same_last_four_as_server_card_but_different_expiration_date(
              true)
          .with_card_save_type(CardSaveType::kCardSaveOnly));

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat(
          {GetBaseHistogramName(), ".WithSameLastFourButDifferentExpiration"}),
      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCreditCardPromptOfferMetricTest,
       LogsBubbleShown_ForAllRelevantSubHistograms) {
  if (!IsUploadSave()) {
    GTEST_SKIP() << "Not applicable for local save, as legal lines are "
                    "present only in server save scenarios.";
  }

  base::HistogramTester histogram_tester;
  TriggerFlow(
      /*show_prompt=*/true,
      SaveCreditCardOptions()
          .with_should_request_name_from_user(true)
          .with_has_multiple_legal_lines(true)
          .with_card_save_type(CardSaveType::kCardSaveOnly));

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".RequestingCardholderName"}),
      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".WithMultipleLegalLines"}),
      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCreditCardPromptOfferMetricTest, LogsBubbleNotShown) {
  base::HistogramTester histogram_tester;
  TriggerFlow(
      /*show_prompt=*/false,
      SaveCreditCardOptions().with_card_save_type(CardSaveType::kCardSaveOnly));

  histogram_tester.ExpectUniqueSample(
      GetBaseHistogramName(), SaveCardPromptOffer::kNotShownMaxStrikesReached,
      1);
}

TEST_P(SaveCreditCardPromptOfferMetricTest, DoNotLogBubbleReshown) {
  base::HistogramTester histogram_tester;
  TriggerFlow(/*show_prompt=*/true, SaveCreditCardOptions().with_card_save_type(
                                        CardSaveType::kCardSaveOnly));
  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);

  CloseAndReshowBubble();
  // Verify that `kShown` metrics is not logged again on reshow.
  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptOffer::kShown, 1);
}

class SaveCreditCardPromptResultDesktopMetricTestWithUiCloseReasonParameterized
    : public SaveCardBubbleControllerImplTest,
      public testing::WithParamInterface<std::tuple<PaymentsUiClosedReason>> {
 public:
  SaveCardPromptResultDesktop GetExpectedSaveCardPromptResult() const {
    switch (GetPaymentsUiClosedReason()) {
      case PaymentsUiClosedReason::kUnknown:
        return SaveCardPromptResultDesktop::kUnknown;
      case PaymentsUiClosedReason::kAccepted:
        return SaveCardPromptResultDesktop::kAccepted;
      case PaymentsUiClosedReason::kCancelled:
        return SaveCardPromptResultDesktop::kCancelled;
      case PaymentsUiClosedReason::kClosed:
        return SaveCardPromptResultDesktop::kClosed;
      case PaymentsUiClosedReason::kNotInteracted:
        return SaveCardPromptResultDesktop::kNotInteracted;
      case PaymentsUiClosedReason::kLostFocus:
        return SaveCardPromptResultDesktop::kLostFocus;
    }
  }

  PaymentsUiClosedReason GetPaymentsUiClosedReason() const {
    return std::get<0>(GetParam());
  }
};

INSTANTIATE_TEST_SUITE_P(
    ,
    SaveCreditCardPromptResultDesktopMetricTestWithUiCloseReasonParameterized,
    testing::Combine(testing::Values(PaymentsUiClosedReason::kUnknown,
                                     PaymentsUiClosedReason::kAccepted,
                                     PaymentsUiClosedReason::kCancelled,
                                     PaymentsUiClosedReason::kClosed,
                                     PaymentsUiClosedReason::kNotInteracted,
                                     PaymentsUiClosedReason::kLostFocus)));

// Tests that correct SaveCardPromptResultDesktop metric is logged based on the
// PaymentsUiClosedReason for local save.
TEST_P(
    SaveCreditCardPromptResultDesktopMetricTestWithUiCloseReasonParameterized,
    LogsLocalSaveCreditCardPromptResult) {
  base::HistogramTester histogram_tester;
  ShowLocalBubble(/*card=*/nullptr,
                  SaveCreditCardOptions()
                      .with_card_save_type(CardSaveType::kCardSaveOnly)
                      .with_show_prompt(true));
  CloseBubble(GetPaymentsUiClosedReason());

  histogram_tester.ExpectUniqueSample(
      base::StrCat({kSaveCardPromptResultDesktopBaseHistogram, ".Local"}),
      GetExpectedSaveCardPromptResult(), 1);
}

// Tests that correct SaveCardPromptResultDesktop metric is logged based on the
// PaymentsUiClosedReason for server save.
TEST_P(
    SaveCreditCardPromptResultDesktopMetricTestWithUiCloseReasonParameterized,
    LogsServerSaveCreditCardPromptResult) {
  base::HistogramTester histogram_tester;
  ShowUploadBubble(SaveCreditCardOptions()
                       .with_card_save_type(CardSaveType::kCardSaveOnly)
                       .with_show_prompt(true));
  if (GetPaymentsUiClosedReason() == PaymentsUiClosedReason::kAccepted) {
    // `Upload` dialog shows loading on being accepted and doesn't close by
    // itself. If the loading dialog is closed, user's acceptance should still
    // be recorded and the closure will be recorded as loading prompt's result.
    controller()->OnSaveButton({});
    CloseBubble(PaymentsUiClosedReason::kClosed);
  } else {
    CloseBubble(GetPaymentsUiClosedReason());
  }

  histogram_tester.ExpectUniqueSample(
      base::StrCat({kSaveCardPromptResultDesktopBaseHistogram, ".Server"}),
      GetExpectedSaveCardPromptResult(), 1);
}

// Test class to verify that all relevant sub histograms are logged based on the
// SaveCreditCardOptions for the SaveCardPromptResultDesktop metric.
class SaveCreditCardPromptResultDesktopMetricTestParameterized
    : public SaveCardBubbleControllerImplTest,
      public ::testing::WithParamInterface</*is_upload_save*/ bool> {
 public:
  void TriggerFlow(SaveCreditCardOptions options = {}) {
    if (IsUploadSave()) {
      ShowUploadBubble(options.with_show_prompt(true));
    } else {
      ShowLocalBubble(/*card=*/nullptr, options.with_show_prompt(true));
    }
  }

  std::string GetBaseHistogramName() {
    return base::StrCat({kSaveCardPromptResultDesktopBaseHistogram,
                         IsUploadSave() ? ".Server" : ".Local"});
  }

  bool IsUploadSave() { return GetParam(); }
};

INSTANTIATE_TEST_SUITE_P(
    ,
    SaveCreditCardPromptResultDesktopMetricTestParameterized,
    testing::Bool());

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsSaveCardResult_WhenRequestingCardHolderName) {
  base::HistogramTester histogram_tester;
  TriggerFlow(SaveCreditCardOptions()
                  .with_should_request_name_from_user(true)
                  .with_card_save_type(CardSaveType::kCardSaveOnly));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".RequestingCardholderName"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsSaveCardResult_WhenRequestingExpirationDate) {
  base::HistogramTester histogram_tester;
  TriggerFlow(SaveCreditCardOptions()
                  .with_should_request_expiration_date_from_user(true)
                  .with_card_save_type(CardSaveType::kCardSaveOnly));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".RequestingExpirationDate"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsSaveCardResult_WhenSavingWithCvc) {
  base::HistogramTester histogram_tester;
  TriggerFlow(SaveCreditCardOptions().with_card_save_type(
      CardSaveType::kCardSaveWithCvc));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".SavingWithCvc"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsSaveCardResult_ForPromptWithMultipleLegalLines) {
  if (!IsUploadSave()) {
    GTEST_SKIP() << "Not applicable for local save, as legal lines are "
                    "present only in server save scenarios";
  }

  base::HistogramTester histogram_tester;
  TriggerFlow(SaveCreditCardOptions()
                  .with_has_multiple_legal_lines(true)
                  .with_card_save_type(CardSaveType::kCardSaveOnly));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".WithMultipleLegalLines"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsSaveCardResult_ForPromptWithLegalLinesMentioningPersonalization) {
  if (!IsUploadSave()) {
    GTEST_SKIP() << "Not applicable for local save, as legal lines are "
                    "present only in server save scenarios";
  }
  base::test::ScopedFeatureList feature_list{
      features::kAutofillParseLegalMessageLines};

  base::HistogramTester histogram_tester;
  TriggerFlow(SaveCreditCardOptions()
                  .with_legal_lines_mention_personalization(true)
                  .with_card_save_type(CardSaveType::kCardSaveOnly));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat(
          {GetBaseHistogramName(), ".LegalMessageLinesMentionPersonalization"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsSaveCardResult_ForCardWithSameLastFourButDifferentExpiration) {
  if (!IsUploadSave()) {
    GTEST_SKIP() << "Not applicable for local save, as the condition (same "
                    "last four digits, different expiration date) is only "
                    "possible for server save scenarios.";
  }

  base::HistogramTester histogram_tester;
  TriggerFlow(
      SaveCreditCardOptions()
          .with_same_last_four_as_server_card_but_different_expiration_date(
              true)
          .with_card_save_type(CardSaveType::kCardSaveOnly));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat(
          {GetBaseHistogramName(), ".WithSameLastFourButDifferentExpiration"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsSaveCardResult_WhenUserHasSavedCards) {
  base::HistogramTester histogram_tester;
  personal_data_manager()->test_payments_data_manager().ClearCreditCards();
  AddCreditCard(test::GetCreditCard());
  TriggerFlow(
      SaveCreditCardOptions().with_card_save_type(CardSaveType::kCardSaveOnly));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".UserHasSavedCards"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsSaveCardResult_WhenUserHasNoCards) {
  base::HistogramTester histogram_tester;
  TriggerFlow(
      SaveCreditCardOptions().with_card_save_type(CardSaveType::kCardSaveOnly));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".UserHasNoCards"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsSaveCardResult_ForAllRelevantSubHistograms) {
  base::HistogramTester histogram_tester;

  TriggerFlow(SaveCreditCardOptions()
                  .with_card_save_type(CardSaveType::kCardSaveWithCvc)
                  .with_should_request_name_from_user(true)
                  .with_show_prompt(true));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".SavingWithCvc"}),
      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".RequestingCardholderName"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

TEST_P(SaveCreditCardPromptResultDesktopMetricTestParameterized,
       LogsUploadSaveCardResult_ForAllRelevantSubHistograms) {
  if (!IsUploadSave()) {
    GTEST_SKIP() << "Not applicable for local save, as legal lines are "
                    "present only in server save scenarios.";
  }

  base::HistogramTester histogram_tester;
  TriggerFlow(SaveCreditCardOptions()
                  .with_card_save_type(CardSaveType::kCardSaveWithCvc)
                  .with_should_request_name_from_user(true)
                  .with_has_multiple_legal_lines(true)
                  .with_show_prompt(true));
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(GetBaseHistogramName(),
                                      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".SavingWithCvc"}),
      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".RequestingCardholderName"}),
      SaveCardPromptResultDesktop::kClosed, 1);
  histogram_tester.ExpectUniqueSample(
      base::StrCat({GetBaseHistogramName(), ".WithMultipleLegalLines"}),
      SaveCardPromptResultDesktop::kClosed, 1);
}

// Tests that SaveCardPromptResultDesktop metric is not logged again when a
// dialog is reshown.
TEST_F(SaveCardBubbleControllerImplTest,
       DoNotLogSaveCreditCardPromptResultOnReshow) {
  base::HistogramTester histogram_tester;
  ShowLocalBubble(/*card=*/nullptr,
                  SaveCreditCardOptions()
                      .with_card_save_type(CardSaveType::kCardSaveOnly)
                      .with_show_prompt(true));
  CloseAndReshowBubble();
  histogram_tester.ExpectUniqueSample(
      base::StrCat({kSaveCardPromptResultDesktopBaseHistogram, ".Local"}),
      SaveCardPromptResultDesktop::kNotInteracted, 1);

  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectBucketCount(
      base::StrCat({kSaveCardPromptResultDesktopBaseHistogram, ".Local"}),
      SaveCardPromptResultDesktop::kClosed, 0);
}

// Param of the SaveCvcBubbleLoggingTest:
// -- std::string show_type: decides if the view is shown first time or
// re-shown.
// -- std::string save_destination decides if card or CVC will be saved locally
// or to the server.
class SaveCvcBubbleLoggingTest
    : public SaveCardBubbleControllerImplTest,
      public testing::WithParamInterface<std::tuple<std::string, std::string>> {
 public:
  SaveCvcBubbleLoggingTest()
      : show_type_(std::get<0>(GetParam())),
        save_destination_(std::get<1>(GetParam())) {}
  ~SaveCvcBubbleLoggingTest() override = default;

  void TriggerFlow(bool show_prompt = true) {
    ASSERT_TRUE(show_type_ == "FirstShow" || show_type_ == "Reshows");
    if (save_destination_ == "Upload") {
      ShowUploadBubble(
          /*options=*/SaveCreditCardOptions()
              .with_card_save_type(CardSaveType::kCvcSaveOnly)
              .with_show_prompt(show_prompt));
    } else {
      ASSERT_EQ(save_destination_, "Local");
      ShowLocalBubble(
          /*card=*/nullptr,
          /*options=*/SaveCreditCardOptions()
              .with_card_save_type(CardSaveType::kCvcSaveOnly)
              .with_show_prompt(show_prompt));
    }

    if (show_type_ == "Reshows") {
      CloseAndReshowBubble();
    }
  }

  const std::string show_type_;
  const std::string save_destination_;
};

INSTANTIATE_TEST_SUITE_P(,
                         SaveCvcBubbleLoggingTest,
                         testing::Combine(testing::Values("FirstShow",
                                                          "Reshows"),
                                          testing::Values("Upload", "Local")));

TEST_P(SaveCvcBubbleLoggingTest, Metrics_ShowBubble) {
  base::HistogramTester histogram_tester;
  TriggerFlow();

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCvcPromptOffer." + save_destination_ + "." + show_type_,
      SaveCardPromptOffer::kShown, 1);
}

TEST_P(SaveCvcBubbleLoggingTest, Metrics_ShowIconOnly) {
  // This case does not happen when it is a reshow.
  if (show_type_ == "Reshows") {
    return;
  }

  base::HistogramTester histogram_tester;
  TriggerFlow(/*show_prompt=*/false);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCvcPromptOffer." + save_destination_ + "." + show_type_,
      SaveCardPromptOffer::kNotShownMaxStrikesReached, 1);
}

TEST_P(SaveCvcBubbleLoggingTest, Metrics_SaveButton) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  controller()->OnSaveButton({});
  CloseBubble(PaymentsUiClosedReason::kAccepted);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCvcPromptResult." + save_destination_ + "." + show_type_,
      autofill_metrics::LegacySaveCardPromptResult::kAccepted, 1);
}

TEST_P(SaveCvcBubbleLoggingTest, Metrics_CancelButton) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kCancelled);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCvcPromptResult." + save_destination_ + "." + show_type_,
      autofill_metrics::LegacySaveCardPromptResult::kCancelled, 1);
}

TEST_P(SaveCvcBubbleLoggingTest, Metrics_Closed) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kClosed);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCvcPromptResult." + save_destination_ + "." + show_type_,
      autofill_metrics::LegacySaveCardPromptResult::kClosed, 1);
}

TEST_P(SaveCvcBubbleLoggingTest, Metrics_NotInteracted) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kNotInteracted);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCvcPromptResult." + save_destination_ + "." + show_type_,
      autofill_metrics::LegacySaveCardPromptResult::kNotInteracted, 1);
}

TEST_P(SaveCvcBubbleLoggingTest, Metrics_LostFocus) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kLostFocus);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCvcPromptResult." + save_destination_ + "." + show_type_,
      autofill_metrics::LegacySaveCardPromptResult::kLostFocus, 1);
}

TEST_P(SaveCvcBubbleLoggingTest, Metrics_Unknown) {
  base::HistogramTester histogram_tester;
  TriggerFlow();
  CloseBubble(PaymentsUiClosedReason::kUnknown);

  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCvcPromptResult." + save_destination_ + "." + show_type_,
      autofill_metrics::LegacySaveCardPromptResult::kUnknown, 1);
}

TEST_F(SaveCardBubbleControllerImplTest, LocalCvcOnlySaveDialogContent) {
  // Show the local CVC save bubble.
  ShowLocalBubble(
      /*card=*/nullptr,
      /*options=*/SaveCreditCardOptions()
          .with_card_save_type(CardSaveType::kCvcSaveOnly)
          .with_show_prompt(true));

  ASSERT_EQ(PaymentsBubbleType::kLocalCvcSave,
            controller()->GetPaymentsBubbleType());
  ASSERT_NE(nullptr, controller()->GetPaymentBubbleView());
  EXPECT_EQ(controller()->GetWindowTitle(), u"Save security code?");
  EXPECT_EQ(controller()->GetExplanatoryMessage(),
            u"This card's CVC will be encrypted and saved to your device for "
            u"faster checkout");
}

class SaveCvcBubbleControllerImplTestWithWalletBranding
    : public SaveCardBubbleControllerImplTest {
 public:
  SaveCvcBubbleControllerImplTestWithWalletBranding() {
    scoped_feature_list_.InitAndEnableFeature(
        features::kAutofillEnableWalletBranding);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(SaveCvcBubbleControllerImplTestWithWalletBranding,
       UploadCvcOnlySaveDialogContent) {
  // Show the server CVC save bubble.
  ShowUploadBubble(
      /*options=*/SaveCreditCardOptions()
          .with_card_save_type(CardSaveType::kCvcSaveOnly)
          .with_show_prompt(true));

  ASSERT_EQ(PaymentsBubbleType::kUploadCvcSave,
            controller()->GetPaymentsBubbleType());
  ASSERT_NE(nullptr, controller()->GetPaymentBubbleView());
  EXPECT_EQ(controller()->GetWindowTitle(), u"Save security code?");
  EXPECT_EQ(controller()->GetExplanatoryMessage(),
            l10n_util::GetStringUTF16(
                IDS_AUTOFILL_SAVE_CVC_TO_WALLET_PROMPT_EXPLANATION_UPLOAD));
  EXPECT_TRUE(controller()->GetLegalMessageLines().empty());
}

class SaveCvcBubbleControllerImplTestWithoutWalletBranding
    : public SaveCardBubbleControllerImplTest {
 public:
  SaveCvcBubbleControllerImplTestWithoutWalletBranding() {
    scoped_feature_list_.InitAndDisableFeature(
        features::kAutofillEnableWalletBranding);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(SaveCvcBubbleControllerImplTestWithoutWalletBranding,
       UploadCvcOnlySaveDialogContent) {
  // Show the server CVC save bubble.
  ShowUploadBubble(
      /*options=*/SaveCreditCardOptions()
          .with_card_save_type(CardSaveType::kCvcSaveOnly)
          .with_show_prompt(true));

  ASSERT_EQ(PaymentsBubbleType::kUploadCvcSave,
            controller()->GetPaymentsBubbleType());
  ASSERT_NE(nullptr, controller()->GetPaymentBubbleView());
  EXPECT_EQ(controller()->GetWindowTitle(), u"Save security code?");
  EXPECT_EQ(controller()->GetExplanatoryMessage(),
            l10n_util::GetStringUTF16(
                IDS_AUTOFILL_SAVE_CVC_PROMPT_EXPLANATION_UPLOAD));
  EXPECT_TRUE(controller()->GetLegalMessageLines().empty());
}

TEST_F(SaveCardBubbleControllerImplTest,
       LocalCard_FirstShow_SaveButton_SigninPromo_Close_Reshow_ManageCards) {
  EXPECT_CALL(*mock_sentiment_service_, SavedCard()).Times(1);

  // Show the local card save bubble.
  ShowLocalBubble(
      /*card=*/nullptr,
      /*options=*/SaveCreditCardOptions().with_card_save_type(
          CardSaveType::kCardSaveOnly));
  ClickSaveButton();
  CloseAndReshowBubble();
  // After closing the sign-in promo, clicking the icon should bring up the
  // Manage cards bubble. Verify that the icon tooltip, the title for the
  // bubble, and the save animation reflect the correct info.
  ASSERT_EQ(PaymentsBubbleType::kManageCards,
            controller()->GetPaymentsBubbleType());
  ASSERT_NE(nullptr, controller()->GetPaymentBubbleView());
  EXPECT_EQ(controller()->GetWindowTitle(), u"Card saved");
  EXPECT_EQ(controller()->GetSavePaymentIconTooltipText(), u"Save card");
  EXPECT_EQ(controller()->GetSaveSuccessAnimationStringId(),
            IDS_AUTOFILL_CARD_SAVED);
}

TEST_F(SaveCardBubbleControllerImplTest,
       LocalCvc_FirstShow_SaveButton_SigninPromo_Close_Reshow_ManageCards) {
  EXPECT_CALL(*mock_sentiment_service_, SavedCard()).Times(1);

  // Show the local CVC save bubble.
  ShowLocalBubble(
      /*card=*/nullptr,
      /*options=*/SaveCreditCardOptions().with_card_save_type(
          CardSaveType::kCvcSaveOnly));
  ClickSaveButton();
  CloseAndReshowBubble();
  // After closing the sign-in promo, clicking the icon should bring up the
  // Manage cards bubble. Verify that the icon tooltip, the title for the
  // bubble, and the save animation reflect the correct info.
  ASSERT_EQ(PaymentsBubbleType::kManageCards,
            controller()->GetPaymentsBubbleType());
  ASSERT_NE(nullptr, controller()->GetPaymentBubbleView());
  EXPECT_EQ(controller()->GetWindowTitle(), u"CVC saved");
  EXPECT_EQ(controller()->GetSavePaymentIconTooltipText(), u"Save CVC");
  EXPECT_EQ(controller()->GetSaveSuccessAnimationStringId(),
            IDS_AUTOFILL_CVC_SAVED);
}

TEST_F(SaveCardBubbleControllerImplTest,
       Metrics_Local_ClickManageCardsDoneButton) {
  EXPECT_CALL(*mock_sentiment_service_, SavedCard()).Times(1);
  base::HistogramTester histogram_tester;
  ShowLocalBubble();
  ClickSaveButton();
  CloseAndReshowBubble();
  ASSERT_EQ(PaymentsBubbleType::kManageCards,
            controller()->GetPaymentsBubbleType());

  ClickSaveButton();
  EXPECT_THAT(
      histogram_tester.GetAllSamples("Autofill.ManageCardsPrompt"),
      ElementsAre(Bucket(ManageCardsPromptMetric::kManageCardsShown, 1),
                  Bucket(ManageCardsPromptMetric::kManageCardsDone, 1)));
}

TEST_F(SaveCardBubbleControllerImplTest,
       Metrics_Local_ClickManageCardsManageCardsButton) {
  EXPECT_CALL(*mock_sentiment_service_, SavedCard()).Times(1);
  base::HistogramTester histogram_tester;
  ShowLocalBubble();
  ClickSaveButton();
  CloseAndReshowBubble();
  controller()->OnManageCardsClicked();
  EXPECT_THAT(
      histogram_tester.GetAllSamples("Autofill.ManageCardsPrompt"),
      ElementsAre(Bucket(ManageCardsPromptMetric::kManageCardsShown, 1),
                  Bucket(ManageCardsPromptMetric::kManageCardsManageCards, 1)));
}

TEST_F(
    SaveCardBubbleControllerImplTest,
    Metrics_Local_FirstShow_SaveButton_Close_Reshow_Close_Reshow_ManageCards) {
  EXPECT_CALL(*mock_sentiment_service_, SavedCard()).Times(1);
  base::HistogramTester histogram_tester;
  ShowLocalBubble();
  ClickSaveButton();
  CloseAndReshowBubble();
  CloseAndReshowBubble();
  // After closing the sign-in promo, clicking the icon should bring
  // up the Manage cards bubble.
  EXPECT_THAT(
      histogram_tester.GetAllSamples("Autofill.ManageCardsPrompt"),
      ElementsAre(Bucket(ManageCardsPromptMetric::kManageCardsShown, 2)));
}

TEST_F(
    SaveCardBubbleControllerImplTest,
    Metrics_Local_FirstShow_SaveButton_SigninPromo_Close_Reshow_ManageCards) {
  EXPECT_CALL(*mock_sentiment_service_, SavedCard()).Times(1);
  base::HistogramTester histogram_tester;
  ShowLocalBubble();
  ClickSaveButton();
  CloseAndReshowBubble();
  // After closing the sign-in promo, clicking the icon should bring
  // up the Manage cards bubble.
  EXPECT_THAT(
      histogram_tester.GetAllSamples("Autofill.ManageCardsPrompt"),
      ElementsAre(Bucket(ManageCardsPromptMetric::kManageCardsShown, 1)));
}

TEST_F(SaveCardBubbleControllerImplTest,
       Upload_FirstShow_SaveButton_NoSigninPromo) {
  EXPECT_CALL(*mock_sentiment_service_, SavedCard()).Times(1);
  ShowUploadBubble();
  ClickSaveButton(/*is_upload=*/true);

  EXPECT_FALSE(controller()->IsIconVisible());
  EXPECT_EQ(nullptr, controller()->GetPaymentBubbleView());
}

TEST_F(SaveCardBubbleControllerImplTest,
       Metrics_Upload_FirstShow_SaveButton_NoSigninPromo) {
  EXPECT_CALL(*mock_sentiment_service_, SavedCard()).Times(1);
  base::HistogramTester histogram_tester;
  ShowUploadBubble();
  ClickSaveButton(/*is_upload=*/true);
  // No other bubbles should have popped up.
  histogram_tester.ExpectTotalCount("Autofill.SignInPromo", 0);
  histogram_tester.ExpectTotalCount("Autofill.ManageCardsPrompt", 0);
}

// Test the entire upload save flow with the ShowConfirmationBubbleView()
// callback.
TEST_F(SaveCardBubbleControllerImplTest,
       Upload_OnSave_ShowConfirmationBubbleView) {
  ShowUploadBubble();
  EXPECT_EQ(controller()->GetPaymentsBubbleType(),
            PaymentsBubbleType::kUploadSave);
  EXPECT_TRUE(controller()->IsIconVisible());
  EXPECT_TRUE(IsSaveCardBubbleVisible());

  controller()->OnSaveButton({});
  EXPECT_EQ(controller()->GetPaymentsBubbleType(),
            PaymentsBubbleType::kUploadInProgress);
  EXPECT_TRUE(IsSaveCardBubbleVisible());
  EXPECT_FALSE(IsConfirmationBubbleVisible());

  ShowConfirmationBubbleView(/*card_saved=*/true);
  EXPECT_EQ(controller()->GetPaymentsBubbleType(),
            PaymentsBubbleType::kUploadComplete);
  EXPECT_FALSE(IsSaveCardBubbleVisible());
  EXPECT_TRUE(IsConfirmationBubbleVisible());
  EXPECT_TRUE(controller()->GetConfirmationUiParams().is_success);

  controller()->HideSaveCardBubble();
  EXPECT_EQ(controller()->GetPaymentsBubbleType(),
            PaymentsBubbleType::kInactive);
  EXPECT_FALSE(IsConfirmationBubbleVisible());
  EXPECT_FALSE(controller()->IsIconVisible());
}

// Test that when passing in "card_saved=false" for ShowConfirmationBubbleView()
// the confirmation UI model has "is_success" set to false.
TEST_F(SaveCardBubbleControllerImplTest,
       Upload_OnShowConfirmation_ShowFailureUIModel) {
  ShowConfirmationBubbleView(/*card_saved=*/false);
  EXPECT_FALSE(IsSaveCardBubbleVisible());
  EXPECT_TRUE(IsConfirmationBubbleVisible());
  EXPECT_EQ(controller()->GetPaymentsBubbleType(),
            PaymentsBubbleType::kUploadComplete);
  EXPECT_FALSE(controller()->GetConfirmationUiParams().is_success);
}

// Test that when showing the upload bubble when the confirmation bubble view is
// still up, the confirmation bubble view is closed and the upload bubble view
// is still shown.
TEST_F(SaveCardBubbleControllerImplTest,
       Upload_OnShowConfirmationBubbleView_ThenShowUploadView) {
  ShowConfirmationBubbleView(/*card_saved=*/true);
  EXPECT_EQ(controller()->GetPaymentsBubbleType(),
            PaymentsBubbleType::kUploadComplete);
  EXPECT_TRUE(IsConfirmationBubbleVisible());
  EXPECT_TRUE(controller()->GetConfirmationUiParams().is_success);

  ShowUploadBubble();
  EXPECT_EQ(controller()->GetPaymentsBubbleType(),
            PaymentsBubbleType::kUploadSave);
  EXPECT_TRUE(IsSaveCardBubbleVisible());
  EXPECT_FALSE(IsConfirmationBubbleVisible());
  EXPECT_TRUE(controller()->IsIconVisible());
}

// Test that the `Accepted` upload result metric is recorded on upload card save
// and that upload result metrics are not recorded but the confirmation shown &
// result metrics are recorded when the save card bubble is closed after the
// save card upload completes.
TEST_F(SaveCardBubbleControllerImplTest, Metrics_Upload_AfterSave_OnClose) {
  base::HistogramTester histogram_tester;

  ShowUploadBubble();
  controller()->OnSaveButton({});

  histogram_tester.ExpectUniqueSample(
      base::StrCat({kSaveCardPromptResultDesktopBaseHistogram, ".Server"}),
      SaveCardPromptResultDesktop::kAccepted, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Upload.FirstShow",
      autofill_metrics::LegacySaveCardPromptResult::kAccepted, 1);

  ShowConfirmationBubbleView(/*card_saved=*/true);
  CloseBubble();

  histogram_tester.ExpectUniqueSample(
      "Autofill.CreditCardUpload.ConfirmationShown.CardUploaded", true, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.CreditCardUpload.ConfirmationResult.CardUploaded",
      autofill_metrics::LegacySaveCardPromptResult::kNotInteracted, 1);
  // Expect that save card accepted metric is recorded just once from the save
  // button interaction.
  histogram_tester.ExpectTotalCount(
      base::StrCat({kSaveCardPromptResultDesktopBaseHistogram, ".Server"}), 1);
  histogram_tester.ExpectTotalCount(
      "Autofill.SaveCreditCardPromptResult.Upload.FirstShow", 1);
}

// Test that the `CardNotUploaded` confirmation shown & result metrics are
// recorded when the save card bubble is closed after the save card upload
// completes without the card being saved.
TEST_F(SaveCardBubbleControllerImplTest,
       Metrics_Upload_AfterFailedSave_OnClose) {
  base::HistogramTester histogram_tester;

  ShowUploadBubble();
  controller()->OnSaveButton({});
  ShowConfirmationBubbleView(/*card_saved=*/false);
  CloseBubble();

  histogram_tester.ExpectUniqueSample(
      "Autofill.CreditCardUpload.ConfirmationShown.CardNotUploaded", true, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.CreditCardUpload.ConfirmationResult.CardNotUploaded",
      autofill_metrics::LegacySaveCardPromptResult::kNotInteracted, 1);
}

// Test that the `Accepted` upload result metric is not recorded and the loading
// view shown & closed metrics are recorded when the save card bubble is closed
// before the save card upload completes.
TEST_F(SaveCardBubbleControllerImplTest, Metrics_Upload_DuringSave_OnClose) {
  base::HistogramTester histogram_tester;

  ShowUploadBubble();
  controller()->OnSaveButton({});

  histogram_tester.ExpectUniqueSample(
      base::StrCat({kSaveCardPromptResultDesktopBaseHistogram, ".Server"}),
      SaveCardPromptResultDesktop::kAccepted, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptResult.Upload.FirstShow",
      autofill_metrics::LegacySaveCardPromptResult::kAccepted, 1);

  CloseBubble();

  histogram_tester.ExpectUniqueSample("Autofill.CreditCardUpload.LoadingShown",
                                      true, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.CreditCardUpload.LoadingResult",
      autofill_metrics::LegacySaveCardPromptResult::kNotInteracted, 1);
  // Expect that save card accepted metric is recorded just once from the save
  // button interaction.
  histogram_tester.ExpectTotalCount(
      base::StrCat({kSaveCardPromptResultDesktopBaseHistogram, ".Server"}), 1);
  histogram_tester.ExpectTotalCount(
      "Autofill.SaveCreditCardPromptResult.Upload.FirstShow", 1);
}

// Test that metrics are not recorded in
// SaveCardBubbleController::OnSaveButton() on local card save.
TEST_F(SaveCardBubbleControllerImplTest, Metrics_Local_OnSave) {
  base::HistogramTester histogram_tester;

  ShowLocalBubble();
  controller()->OnSaveButton({});

  histogram_tester.ExpectTotalCount(
      base::StrCat({kSaveCardPromptResultDesktopBaseHistogram, ".Server"}), 0);
  histogram_tester.ExpectTotalCount(
      "Autofill.SaveCreditCardPromptResult.Local.FirstShow", 0);
}

// Test that after changing tabs, when returning to the tab with the save card,
// the bubble view is no longer showing but can be accessed through the icon.
TEST_F(SaveCardBubbleControllerImplTest, VisibilityChange_Upload_HideBubble) {
  base::HistogramTester histogram_tester;

  ShowUploadBubble();
  EXPECT_TRUE(IsSaveCardBubbleVisible());

  // Simulate switching to a different tab.
  SimulateTabVisibilityChange(content::Visibility::HIDDEN);
  EXPECT_FALSE(IsSaveCardBubbleVisible());

  histogram_tester.ExpectTotalCount(
      "Autofill.SaveCreditCardPromptResult.Upload.FirstShow", 1);

  // Simulate returning to tab where bubble was previously shown.
  SimulateTabVisibilityChange(content::Visibility::VISIBLE);

  EXPECT_FALSE(IsSaveCardBubbleVisible());
  EXPECT_TRUE(controller()->IsIconVisible());
}

// Test that after a link is clicked in the save card bubble view; and one
// returns to the tab with the save card, the bubble view is automatically
// re-shown without user prompt.
TEST_F(SaveCardBubbleControllerImplTest,
       VisibilityChange_Upload_ReshowAfterLinkClick) {
  tabs::TabInterface* tab = active_tab();

  ShowUploadBubble();
  EXPECT_TRUE(IsSaveCardBubbleVisible());

  controller()->OnLegalMessageLinkClicked(GURL("about:blank"));

  // Reactivate the original tab.
  int index = tab_strip_model_->GetIndexOfTab(tab);
  tab_activity_simulator_.SwitchToTabAt(tab_strip_model_.get(), index);

  // Check that the bubble is shown when returning to the tab which previously
  // showed the bubble.
  EXPECT_TRUE(IsSaveCardBubbleVisible());
  EXPECT_TRUE(controller()->IsIconVisible());

  // Check that the WebContents showing a subsequent time does not show the
  // bubble view.
  SimulateTabVisibilityChange(content::Visibility::HIDDEN);
  EXPECT_FALSE(IsSaveCardBubbleVisible());

  SimulateTabVisibilityChange(content::Visibility::VISIBLE);
  EXPECT_FALSE(IsSaveCardBubbleVisible());
  EXPECT_TRUE(controller()->IsIconVisible());
}

// Test that while in the kUploadInProgress state, after changing tabs and
// returning to the tab with the save card, the state will remain as
// kUploadInProgress.
TEST_F(SaveCardBubbleControllerImplTest,
       VisibilityChange_Upload_InProgressState_Retained) {
  ShowUploadBubble();
  controller()->OnSaveButton({});
  EXPECT_TRUE(IsSaveCardBubbleVisible());
  EXPECT_EQ(controller()->GetPaymentsBubbleType(),
            PaymentsBubbleType::kUploadInProgress);

  // Simulate switching to a different tab and back to the original tab.
  SimulateTabVisibilityChange(content::Visibility::HIDDEN);
  EXPECT_FALSE(IsSaveCardBubbleVisible());
  SimulateTabVisibilityChange(content::Visibility::VISIBLE);

  EXPECT_EQ(controller()->GetPaymentsBubbleType(),
            PaymentsBubbleType::kUploadInProgress);
}

// Test that while in the kUploadInProgress state, if the tab is changed and
// the upload is completed, upon returning to the original tab with the save
// card, the confirmation bubble will be showing.
TEST_F(SaveCardBubbleControllerImplTest,
       VisibilityChange_Upload_InProgressStateTransitionIntoCompletedState) {
  tabs::TabInterface* tab = active_tab();

  ShowUploadBubble();
  controller()->OnSaveButton({});
  EXPECT_TRUE(IsSaveCardBubbleVisible());

  // Need to save a reference to the controller to call while on a different tab
  // because controller() grabs the controller from active_web_contents()
  // which is based on the active tab.
  TestSaveCardBubbleControllerImpl* save_card_controller = controller();

  // Switch to a different tab.
  content::WebContents* new_contents = AddTab(GURL("about:blank"));
  int new_index = tab_strip_model_->GetIndexOfWebContents(new_contents);
  tab_activity_simulator_.SwitchToTabAt(tab_strip_model_.get(), new_index);
  EXPECT_FALSE(IsSaveCardBubbleVisible());

  // Simulate that the upload is completed.
  save_card_controller->ShowConfirmationBubbleView(
      /*card_saved=*/true,
      /*is_for_save_and_fill=*/false,
      /*on_confirmation_closed_callback=*/std::nullopt);

  // Expect that the confirmation bubble doesn't show up on the other tab.
  EXPECT_FALSE(IsConfirmationBubbleVisible());

  // Return to the original tab.
  int index = tab_strip_model_->GetIndexOfTab(tab);
  tab_activity_simulator_.SwitchToTabAt(tab_strip_model_.get(), index);

  // Expect that the confirmation bubble is visible.
  EXPECT_TRUE(IsConfirmationBubbleVisible());
}

// Test the metrics for reshowing the bubble view after a link is clicked.
TEST_F(SaveCardBubbleControllerImplTest,
       Metrics_VisibilityChange_Upload_ReshowAfterLinkClick) {
  base::HistogramTester histogram_tester;
  tabs::TabInterface* tab = active_tab();

  ShowUploadBubble();
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptOffer.Upload.FirstShow",
      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptOffer.Upload.Reshows",
      SaveCardPromptOffer::kShown, 0);

  controller()->OnLegalMessageLinkClicked(GURL("about:blank"));

  // Ensure that closing the bubble through clicking a link does not get logged
  // to the metrics.
  histogram_tester.ExpectTotalCount(
      "Autofill.SaveCreditCardPromptResult.Upload.FirstShow", 0);
  histogram_tester.ExpectTotalCount(
      "Autofill.SaveCreditCardPromptResult.Upload.Reshows", 0);

  // Reactivate the original tab.
  int index = tab_strip_model_->GetIndexOfTab(tab);
  tab_activity_simulator_.SwitchToTabAt(tab_strip_model_.get(), index);

  // Expect the prompt metric not to change from the initial bubble showing
  // because this is a reshowing after returning to the original tab after a
  // link click.
  // TODO(crbug.com/316391673): Determine if a different metric (or the re-show
  // metric) should be tracking this re-show.
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptOffer.Upload.FirstShow",
      SaveCardPromptOffer::kShown, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.SaveCreditCardPromptOffer.Upload.Reshows",
      SaveCardPromptOffer::kShown, 0);
}

// Test that `HideSaveCardBubble()` hides save card offer and confirmation
// bubble.
TEST_F(SaveCardBubbleControllerImplTest, HideSaveCardBubble) {
  ShowUploadBubble();
  EXPECT_NE(controller()->GetPaymentBubbleView(), nullptr);

  controller()->HideSaveCardBubble();
  EXPECT_EQ(controller()->GetPaymentBubbleView(), nullptr);

  ShowConfirmationBubbleView(/*card_saved=*/true);
  EXPECT_NE(controller()->GetPaymentBubbleView(), nullptr);

  controller()->HideSaveCardBubble();
  EXPECT_EQ(controller()->GetPaymentBubbleView(), nullptr);
}

// Test that `OnConfirmationClosedCallback` runs when confirmation prompt
// is closed by user.
TEST_F(SaveCardBubbleControllerImplTest,
       OnConfirmationPromptClosedByUser_RunCallback) {
  ShowConfirmationBubbleView(/*card_saved=*/true);
  CloseBubble();
  EXPECT_TRUE(did_on_confirmation_closed_callback_run_);
  EXPECT_EQ(controller()->GetPaymentBubbleView(), nullptr);
}

// Test that `OnConfirmationClosedCallback` runs when confirmation prompt is
// auto-closed in 3 sec.
TEST_F(SaveCardBubbleControllerImplTest,
       OnConfirmationPromptAutoClosed_RunCallback) {
  ShowConfirmationBubbleView(/*card_saved=*/true);
  task_environment()->FastForwardBy(
      SaveCardBubbleControllerImpl::kAutoCloseConfirmationBubbleWaitSec);
  EXPECT_TRUE(did_on_confirmation_closed_callback_run_);
  EXPECT_EQ(controller()->GetPaymentBubbleView(), nullptr);
}

TEST_F(SaveCardBubbleControllerImplTest, ReturnsApplicableWindowTitle) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/
      {features::kAutofillEnableWalletBranding,
       features::kAutofillEnableWalletBrandingV2},
      /*disabled_features=*/{});

  ShowUploadBubble();
  EXPECT_EQ(l10n_util::GetStringUTF16(
                IDS_AUTOFILL_SAVE_CARD_IN_GOOGLE_WALLET_PROMPT_TITLE),
            controller()->GetWindowTitle());
}

TEST_F(SaveCardBubbleControllerImplTest,
       ReturnsApplicableWindowTitle_WalletBrandingV2Disabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableWalletBranding},
      /*disabled_features=*/{features::kAutofillEnableWalletBrandingV2});

  ShowUploadBubble();
  EXPECT_EQ(l10n_util::GetStringUTF16(
                IDS_AUTOFILL_SAVE_CARD_PROMPT_TITLE_TO_CLOUD_SECURITY),
            controller()->GetWindowTitle());
}

TEST_F(SaveCardBubbleControllerImplTest, ReturnsApplicableExplanatoryMessage) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/
      {features::kAutofillEnableWalletBranding,
       features::kAutofillEnableWalletBrandingV2},
      /*disabled_features=*/{});

  ShowUploadBubble();
  EXPECT_EQ(l10n_util::GetStringUTF16(
                IDS_AUTOFILL_SAVE_CARD_PROMPT_UPLOAD_TO_WALLET_V2_EXPLANATION),
            controller()->GetExplanatoryMessage());
}

TEST_F(SaveCardBubbleControllerImplTest,
       ReturnsApplicableExplanatoryMessage_WalletBrandingV2Disabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableWalletBranding},
      /*disabled_features=*/{features::kAutofillEnableWalletBrandingV2});

  ShowUploadBubble();
  EXPECT_EQ(
      l10n_util::GetStringUTF16(
          IDS_AUTOFILL_SAVE_CARD_PROMPT_UPLOAD_TO_WALLET_EXPLANATION_SECURITY),
      controller()->GetExplanatoryMessage());
}

TEST_F(SaveCardBubbleControllerImplTest,
       ReturnsApplicableExplanatoryMessage_WalletBrandingDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kAutofillEnableWalletBranding,
                             features::kAutofillEnableWalletBrandingV2});

  ShowUploadBubble();
  EXPECT_EQ(l10n_util::GetStringUTF16(
                IDS_AUTOFILL_SAVE_CARD_PROMPT_UPLOAD_EXPLANATION_SECURITY),
            controller()->GetExplanatoryMessage());
}

class SaveCardBubbleControllerImplTestWithCvCStorageAndFilling
    : public SaveCardBubbleControllerImplTest {

};

TEST_F(SaveCardBubbleControllerImplTestWithCvCStorageAndFilling,
       LocalCardSaveOnlyDialogContent) {
  // Show the local card save bubble.
  ShowLocalBubble(
      /*card=*/nullptr,
      /*options=*/SaveCreditCardOptions()
          .with_card_save_type(CardSaveType::kCardSaveOnly)
          .with_show_prompt(true));

  ASSERT_EQ(PaymentsBubbleType::kLocalSave,
            controller()->GetPaymentsBubbleType());
  ASSERT_NE(nullptr, controller()->GetPaymentBubbleView());
  EXPECT_EQ(controller()->GetWindowTitle(), u"Save card?");
  EXPECT_EQ(controller()->GetExplanatoryMessage(),
            u"To pay faster next time, save your card to your device");
}

TEST_F(SaveCardBubbleControllerImplTestWithCvCStorageAndFilling,
       LocalCardSaveWithCvcDialogContent) {
  // Show the local card save with CVC bubble.
  ShowLocalBubble(
      /*card=*/nullptr,
      /*options=*/SaveCreditCardOptions()
          .with_card_save_type(CardSaveType::kCardSaveWithCvc)
          .with_show_prompt(true));

  ASSERT_EQ(PaymentsBubbleType::kLocalSave,
            controller()->GetPaymentsBubbleType());
  ASSERT_NE(nullptr, controller()->GetPaymentBubbleView());
  EXPECT_EQ(controller()->GetWindowTitle(), u"Save card?");
  EXPECT_EQ(controller()->GetExplanatoryMessage(),
            u"To pay faster next time, save your card and encrypted security "
            u"code to your device");
}

}  // namespace
}  // namespace autofill
