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

#include "chrome/browser/facilitated_payments/ui/chrome_facilitated_payments_client.h"

#include <memory>

#include "base/functional/bind.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/facilitated_payments/core/browser/facilitated_payments_app_info_list.h"
#include "components/facilitated_payments/core/browser/pix_account_linking_manager.h"
#include "components/facilitated_payments/core/features/features.h"
#include "components/optimization_guide/core/hints/mock_optimization_guide_decider.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"

using ::testing::_;
using ::testing::Return;

class MockFacilitatedPaymentsController : public FacilitatedPaymentsController {
 public:
  explicit MockFacilitatedPaymentsController(content::WebContents* web_contents)
      : FacilitatedPaymentsController(web_contents) {}
  ~MockFacilitatedPaymentsController() override = default;

  MOCK_METHOD(bool, IsInLandscapeMode, (), (override));
  MOCK_METHOD(void,
              Show,
              (base::span<const autofill::BankAccount> bank_account_suggestions,
               base::OnceCallback<void(int64_t)> on_payment_account_selected),
              (override));
  MOCK_METHOD(
      void,
      ShowForPaymentLink,
      (base::span<const autofill::Ewallet> ewallet_suggestions,
       std::unique_ptr<payments::facilitated::FacilitatedPaymentsAppInfoList>
           app_suggestions,
       base::OnceCallback<void(payments::facilitated::SelectedFopData)>
           on_fop_selected),
      (override));
  MOCK_METHOD(void, ShowProgressScreen, (), (override));
  MOCK_METHOD(void, ShowErrorScreen, (), (override));
  MOCK_METHOD(void, Dismiss, (), (override));
  MOCK_METHOD(void,
              ShowPixAccountLinkingPrompt,
              (int strike_count,
               const std::string& account_email,
               base::OnceCallback<void()> on_accepted,
               base::OnceCallback<void()> on_declined),
              (override));
  MOCK_METHOD(void,
              ShowAccountLinkingFailureNotification,
              (payments::facilitated::FacilitatedPaymentsType),
              (override));
};

class MockPixAccountLinkingManager
    : public payments::facilitated::PixAccountLinkingManager {
 public:
  explicit MockPixAccountLinkingManager(
      payments::facilitated::FacilitatedPaymentsClient* client)
      : PixAccountLinkingManager(client) {}
  ~MockPixAccountLinkingManager() override = default;

  MOCK_METHOD(void,
              MaybeShowPixAccountLinkingPrompt,
              (const url::Origin& pix_payment_page_origin),
              (override));
  MOCK_METHOD(void, DismissPrompt, (), (override));
};

class ChromeFacilitatedPaymentsClientTest
    : public ChromeRenderViewHostTestHarness {
 public:
  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    client_ = std::make_unique<ChromeFacilitatedPaymentsClient>(
        web_contents(), &optimization_guide_decider_);
    auto controller =
        std::make_unique<MockFacilitatedPaymentsController>(web_contents());
    controller_ = controller.get();
    client_->SetFacilitatedPaymentsControllerForTesting(std::move(controller));
    auto pix_account_linking_manager =
        std::make_unique<MockPixAccountLinkingManager>(client_.get());
    pix_account_linking_manager_ = pix_account_linking_manager.get();
    client_->SetPixAccountLinkingManagerForTesting(
        std::move(pix_account_linking_manager));
  }

  auto& base_client() {
    return static_cast<payments::facilitated::FacilitatedPaymentsClient&>(
        *client_);
  }

  MockFacilitatedPaymentsController& controller() { return *controller_; }

  MockPixAccountLinkingManager& pix_account_linking_manager() {
    return *pix_account_linking_manager_;
  }

 protected:
  optimization_guide::MockOptimizationGuideDecider optimization_guide_decider_;
  std::unique_ptr<ChromeFacilitatedPaymentsClient> client_;
  raw_ptr<MockFacilitatedPaymentsController> controller_;
  raw_ptr<MockPixAccountLinkingManager> pix_account_linking_manager_;
};

TEST_F(ChromeFacilitatedPaymentsClientTest, GetPaymentsDataManager) {
  EXPECT_NE(nullptr, base_client().GetPaymentsDataManager());
}

TEST_F(ChromeFacilitatedPaymentsClientTest,
       GetFacilitatedPaymentsNetworkInterface) {
  EXPECT_NE(nullptr, base_client().GetFacilitatedPaymentsNetworkInterface());
}

// Test the client forwards call to show Pix FOP selector to the controller.
TEST_F(ChromeFacilitatedPaymentsClientTest,
       ShowPixPaymentPrompt_ControllerDefaultTrue) {
  EXPECT_CALL(controller(), Show);

  base_client().ShowPixPaymentPrompt({}, base::DoNothing());
}

// Test that the `A2A_MERCHANT_ALLOWLIST`, `EWALLET_MERCHANT_ALLOWLIST`,
// `PIX_PAYMENT_MERCHANT_ALLOWLIST` and `PIX_PSP_ALLOWLIST` optimization types
// are registered when the `ChromeFacilitatedPaymentClient` is created.
TEST_F(ChromeFacilitatedPaymentsClientTest, RegisterAllowlists) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{payments::facilitated::kEwalletPayments,
                            payments::facilitated::kEnableIframeForPix},
      /*disabled_features=*/{});
  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::PIX_MERCHANT_ORIGINS_ALLOWLIST)))
      .Times(1);
  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::A2A_MERCHANT_ALLOWLIST)))
      .Times(1);
  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::EWALLET_MERCHANT_ALLOWLIST)))
      .Times(1);
  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::PIX_PSP_ALLOWLIST)))
      .Times(1);

  // Re-create the client; it should register the allowlist.
  client_ = std::make_unique<ChromeFacilitatedPaymentsClient>(
      web_contents(), &optimization_guide_decider_);
}

// Test that the `EWALLET_MERCHANT_ALLOWLIST` optimization type is not
// registered when the `ChromeFacilitatedPaymentClient` is created and the
// eWallet experiment is disabled.
TEST_F(ChromeFacilitatedPaymentsClientTest, RegisterAllowlists_EWalletExpOff) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(payments::facilitated::kEwalletPayments);

  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::PIX_MERCHANT_ORIGINS_ALLOWLIST)))
      .Times(1);
  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::A2A_MERCHANT_ALLOWLIST)))
      .Times(1);
  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::EWALLET_MERCHANT_ALLOWLIST)))
      .Times(0);

  // Re-create the client; it should not register the allowlist.
  client_ = std::make_unique<ChromeFacilitatedPaymentsClient>(
      web_contents(), &optimization_guide_decider_);
}

// Test that the `PIX_PSP_ALLOWLIST` optimization type is not registered when
// the `ChromeFacilitatedPaymentClient` is created and the iframe experiment is
// disabled.
TEST_F(ChromeFacilitatedPaymentsClientTest, RegisterAllowlists_IframeExpOff) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      payments::facilitated::kEnableIframeForPix);

  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::A2A_MERCHANT_ALLOWLIST)))
      .Times(1);
  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::EWALLET_MERCHANT_ALLOWLIST)))
      .Times(1);
  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::PIX_MERCHANT_ORIGINS_ALLOWLIST)))
      .Times(1);
  EXPECT_CALL(optimization_guide_decider_,
              RegisterOptimizationTypes(testing::ElementsAre(
                  optimization_guide::proto::PIX_PSP_ALLOWLIST)))
      .Times(0);

  // Re-create the client; it should not register the allowlist.
  client_ = std::make_unique<ChromeFacilitatedPaymentsClient>(
      web_contents(), &optimization_guide_decider_);
}

// Test the client forwards call for showing the progress screen to the
// controller.
TEST_F(ChromeFacilitatedPaymentsClientTest, ShowProgressScreen) {
  EXPECT_CALL(controller(), ShowProgressScreen);

  base_client().ShowProgressScreen();
}

// Test the client forwards call for showing the error screen to the controller.
TEST_F(ChromeFacilitatedPaymentsClientTest, ShowErrorScreen) {
  EXPECT_CALL(controller(), ShowErrorScreen);

  base_client().ShowErrorScreen();
}

// Test that the controller is able to process requests to show different
// screens back to back.
TEST_F(ChromeFacilitatedPaymentsClientTest,
       ControllerIsAbleToProcessBackToBackShowRequests) {
  EXPECT_CALL(controller(), Show);
  EXPECT_CALL(controller(), ShowProgressScreen);

  base_client().ShowPixPaymentPrompt({}, base::DoNothing());
  base_client().ShowProgressScreen();
}

// Test that DismissPrompt is called when the client is destroyed.
TEST_F(ChromeFacilitatedPaymentsClientTest,
       Destructor_DismissesPixAccountLinkingPrompt) {
  base_client().InitPixAccountLinkingFlow(
      url::Origin::Create(GURL("https://example.com")));

  EXPECT_CALL(pix_account_linking_manager(), DismissPrompt());

  client_.reset();
}

// Test the client forwards call for closing the bottom sheet to the
// controller.
TEST_F(ChromeFacilitatedPaymentsClientTest, DismissPrompt) {
  EXPECT_CALL(controller(), Dismiss);

  base_client().DismissPrompt();
}

// Test the client forwards call to check the device screen orientation to the
// controller.
TEST_F(ChromeFacilitatedPaymentsClientTest, IsInLandscapeMode) {
  EXPECT_CALL(controller(), IsInLandscapeMode);

  base_client().IsInLandscapeMode();
}

// Test the client correctly returns whether or not the tab is active.
TEST_F(ChromeFacilitatedPaymentsClientTest, IsWebContentsVisibleOrOccluded) {
  web_contents()->UpdateWebContentsVisibility(content::Visibility::VISIBLE);
  EXPECT_TRUE(base_client().IsWebContentsVisibleOrOccluded());

  web_contents()->UpdateWebContentsVisibility(content::Visibility::OCCLUDED);
  EXPECT_TRUE(base_client().IsWebContentsVisibleOrOccluded());

  web_contents()->UpdateWebContentsVisibility(content::Visibility::HIDDEN);
  EXPECT_FALSE(base_client().IsWebContentsVisibleOrOccluded());
}

// Test that the client forwards call to show payment link FOP selector to the
// controller.
TEST_F(ChromeFacilitatedPaymentsClientTest,
       ShowPaymentLinkPrompt_ControllerInvoked) {
  EXPECT_CALL(controller(), ShowForPaymentLink);
  base_client().ShowPaymentLinkPrompt({}, {}, base::DoNothing());
}

// Test that the client forwards call to initiate Pix account linking flow to
// the Pix account linking manager.
TEST_F(ChromeFacilitatedPaymentsClientTest, InitPixAccountLinkingFlow) {
  const url::Origin kPageOrigin =
      url::Origin::Create(GURL("https://example.com"));
  EXPECT_CALL(pix_account_linking_manager(),
              MaybeShowPixAccountLinkingPrompt(kPageOrigin));

  base_client().InitPixAccountLinkingFlow(kPageOrigin);
}

// Test that the client forwards call to show Pix account linking prompt to the
// controller when an account email is available.
TEST_F(ChromeFacilitatedPaymentsClientTest, ShowPixAccountLinkingPrompt) {
  signin::SetPrimaryAccount(IdentityManagerFactory::GetForProfile(profile()),
                            "test@example.com", signin::ConsentLevel::kSignin);
  EXPECT_CALL(controller(),
              ShowPixAccountLinkingPrompt(0, "test@example.com", _, _));

  base_client().ShowPixAccountLinkingPrompt(
      /*strike_count=*/0, base::DoNothing(), base::DoNothing());
}

// Test that the client does not show Pix account linking prompt when no account
// email is available.
TEST_F(ChromeFacilitatedPaymentsClientTest,
       ShowPixAccountLinkingPrompt_NoAccount_DoesNotShowPrompt) {
  EXPECT_CALL(controller(), ShowPixAccountLinkingPrompt).Times(0);

  base_client().ShowPixAccountLinkingPrompt(
      /*strike_count=*/0, base::DoNothing(), base::DoNothing());
}

// Test that the client forwards call to show Pix account linking failure
// notification to the controller.
TEST_F(ChromeFacilitatedPaymentsClientTest,
       ShowAccountLinkingFailureNotification) {
  EXPECT_CALL(controller(),
              ShowAccountLinkingFailureNotification(
                  payments::facilitated::FacilitatedPaymentsType::kPix));

  base_client().ShowAccountLinkingFailureNotification(
      payments::facilitated::FacilitatedPaymentsType::kPix);
}

TEST_F(ChromeFacilitatedPaymentsClientTest,
       IsInChromeCustomTabMode_NullCallback) {
  EXPECT_FALSE(base_client().IsInChromeCustomTabMode());
}

TEST_F(ChromeFacilitatedPaymentsClientTest,
       IsInChromeCustomTabMode_CallbackReturnsTrue) {
  client_ = std::make_unique<ChromeFacilitatedPaymentsClient>(
      web_contents(), &optimization_guide_decider_,
      base::BindRepeating([](content::WebContents*) { return true; }));

  EXPECT_TRUE(base_client().IsInChromeCustomTabMode());
}

TEST_F(ChromeFacilitatedPaymentsClientTest,
       IsInChromeCustomTabMode_CallbackReturnsFalse) {
  client_ = std::make_unique<ChromeFacilitatedPaymentsClient>(
      web_contents(), &optimization_guide_decider_,
      base::BindRepeating([](content::WebContents*) { return false; }));

  EXPECT_FALSE(base_client().IsInChromeCustomTabMode());
}

// Test that initializing ChromeFacilitatedPaymentsClient creates a default
// PixAccountLinkingManager that handles flow initialization without crashing.
TEST_F(ChromeFacilitatedPaymentsClientTest,
       PixAccountLinkingManager_InitializedByDefault) {
  const url::Origin kPageOrigin =
      url::Origin::Create(GURL("https://example.com"));
  auto client = std::make_unique<ChromeFacilitatedPaymentsClient>(
      web_contents(), &optimization_guide_decider_);

  EXPECT_NO_FATAL_FAILURE(static_cast<payments::facilitated::FacilitatedPaymentsClient&>(*client)
      .InitPixAccountLinkingFlow(kPageOrigin));
}
