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

#include "chrome/browser/ui/commerce/discounts_page_action_controller.h"

#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/default_clock.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/commerce/core/mock_account_checker.h"
#include "components/commerce/core/mock_shopping_service.h"
#include "components/commerce/core/test_utils.h"
#include "url/gurl.h"

namespace commerce {

namespace {
const char kShoppingURL[] = "https://example.com";
const char kShoppingURLDomain[] = "example.com";
}  // namespace

class DiscountsPageActionControllerUnittest : public testing::Test {
 public:
  DiscountsPageActionControllerUnittest()
      : shopping_service_(std::make_unique<MockShoppingService>()),
        account_checker_(std::make_unique<MockAccountChecker>()) {
    shopping_service_->SetAccountChecker(account_checker_.get());
  }

  void SetupDiscountResponseForURL(GURL url) {
    double expiry_time_sec =
        (base::DefaultClock::GetInstance()->Now() + base::Days(2))
            .InSecondsFSinceUnixEpoch();

    shopping_service_->SetResponseForGetDiscountInfoForUrl(
        {commerce::CreateValidDiscountInfo(
            /*detail=*/"Get 10% off",
            /*terms_and_conditions=*/"",
            /*value_in_text=*/"$10 off", /*discount_code=*/"discount_code",
            /*id=*/123,
            /*is_merchant_wide=*/true, expiry_time_sec)});
  }

  base::test::FeatureRefAndParams GetNoAutoShownBubbleParam() {
    return {commerce::kDiscountDialogAutoPopupBehaviorSetting,
            {{commerce::kMerchantWideBehaviorParam, "2"},
             {commerce::kNonMerchantWideBehaviorParam, "2"}}};
  }

  base::test::FeatureRefAndParams GetAlwaysAutoShownBubbleParam() {
    return {commerce::kDiscountDialogAutoPopupBehaviorSetting,
            {{commerce::kMerchantWideBehaviorParam, "1"},
             {commerce::kNonMerchantWideBehaviorParam, "1"}}};
  }

  base::test::FeatureRefAndParams GetAutoShownOnceBubbleParam() {
    return {commerce::kDiscountDialogAutoPopupBehaviorSetting,
            {{commerce::kMerchantWideBehaviorParam, "0"},
             {commerce::kNonMerchantWideBehaviorParam, "0"}}};
  }

 protected:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  base::MockRepeatingCallback<void()> notify_host_callback_;
  std::unique_ptr<MockShoppingService> shopping_service_;
  std::unique_ptr<MockAccountChecker> account_checker_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(DiscountsPageActionControllerUnittest, ShouldShowIcon) {
  SetUpDiscountEligibility(&scoped_feature_list_, account_checker_.get(), true);
  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();

  DiscountsPageActionController controller(callback, shopping_service_.get());

  // Before a navigation, the controller should be in an "undecided" state.
  ASSERT_FALSE(controller.ShouldShowForNavigation().has_value());

  EXPECT_CALL(*shopping_service_,
              GetDiscountInfoForUrl(GURL(kShoppingURL), testing::_));
  EXPECT_CALL(notify_host_callback_, Run()).Times(testing::AtLeast(1));
  SetupDiscountResponseForURL(GURL(kShoppingURL));

  // Simulate navigation to |kShoppingURL|
  controller.ResetForNewNavigation(GURL(kShoppingURL));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(controller.ShouldShowForNavigation().has_value());
  EXPECT_TRUE(controller.ShouldShowForNavigation().value());
}

TEST_F(DiscountsPageActionControllerUnittest, ShouldNotShowIcon_NoDiscounts) {
  SetUpDiscountEligibility(&scoped_feature_list_, account_checker_.get(), true);
  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();

  DiscountsPageActionController controller(callback, shopping_service_.get());

  // Before a navigation, the controller should be in an "undecided" state.
  ASSERT_FALSE(controller.ShouldShowForNavigation().has_value());

  EXPECT_CALL(notify_host_callback_, Run()).Times(testing::AtLeast(1));
  EXPECT_CALL(*shopping_service_,
              GetDiscountInfoForUrl(GURL(kShoppingURL), testing::_));
  // Empty response, hence no discounts.
  shopping_service_->SetResponseForGetDiscountInfoForUrl({});

  // Simulate navigation to |kShoppingURL|
  controller.ResetForNewNavigation(GURL(kShoppingURL));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(controller.ShouldShowForNavigation().has_value());
  EXPECT_FALSE(controller.ShouldShowForNavigation().value());
}

TEST_F(DiscountsPageActionControllerUnittest, ShouldNotShowIcon_NoEligible) {
  SetUpDiscountEligibility(&scoped_feature_list_, account_checker_.get(),
                           false);
  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
  DiscountsPageActionController controller(callback, shopping_service_.get());

  EXPECT_CALL(*shopping_service_,
              GetDiscountInfoForUrl(GURL(kShoppingURL), testing::_))
      .Times(0);

  // Simulate navigation to |kShoppingURL|
  controller.ResetForNewNavigation(GURL(kShoppingURL));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(controller.ShouldShowForNavigation().has_value());
  EXPECT_FALSE(controller.ShouldShowForNavigation().value());
}

TEST_F(DiscountsPageActionControllerUnittest, ShouldExpandIcon_ShoppyPageOff) {
  SetUpDiscountEligibility(&scoped_feature_list_, account_checker_.get(), true);
  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();

  DiscountsPageActionController controller(callback, shopping_service_.get());

  // Before a navigation, the controller should be in an "undecided" state.
  ASSERT_FALSE(controller.WantsExpandedUi());

  EXPECT_CALL(notify_host_callback_, Run()).Times(testing::AtLeast(1));
  EXPECT_CALL(*shopping_service_,
              GetDiscountInfoForUrl(GURL(kShoppingURL), testing::_));
  SetupDiscountResponseForURL(GURL(kShoppingURL));

  // Simulate navigation to |kShoppingURL|
  controller.ResetForNewNavigation(GURL(kShoppingURL));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(controller.WantsExpandedUi());
}

TEST_F(DiscountsPageActionControllerUnittest,
       ShouldExpandIcon_ShoppyPageOn_OnNonVisitedDomain) {
  scoped_feature_list_.InitAndEnableFeatureWithParameters(
      kEnableDiscountInfoApi, {{kDiscountOnShoppyPageParam, "true"}});
  SetUpDiscountEligibilityForAccount(account_checker_.get(), true);

  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();

  DiscountsPageActionController controller(callback, shopping_service_.get());

  // Before a navigation, the controller should be in an "undecided" state.
  ASSERT_FALSE(controller.WantsExpandedUi());

  EXPECT_CALL(notify_host_callback_, Run()).Times(testing::AtLeast(1));
  EXPECT_CALL(*shopping_service_,
              GetDiscountInfoForUrl(GURL(kShoppingURL), testing::_));
  SetupDiscountResponseForURL(GURL(kShoppingURL));

  // Simulate navigation to |kShoppingURL|
  controller.ResetForNewNavigation(GURL(kShoppingURL));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(controller.WantsExpandedUi());
  DiscountsPageActionController::DiscountsShownData* shown_data =
      static_cast<DiscountsPageActionController::DiscountsShownData*>(
          shopping_service_->GetUserData(
              DiscountsPageActionController::kDiscountsShownDataKey));

  EXPECT_THAT(shown_data->discount_shown_on_domains,
              testing::Contains(kShoppingURLDomain));
}

TEST_F(DiscountsPageActionControllerUnittest,
       ShouldExpandIcon_ShoppyPageOn_OnBubbleAutoShown) {
  scoped_feature_list_.InitWithFeaturesAndParameters(
      {{kEnableDiscountInfoApi, {{kDiscountOnShoppyPageParam, "true"}}},
       GetAlwaysAutoShownBubbleParam()},
      /*disabled_features=*/{});
  SetUpDiscountEligibilityForAccount(account_checker_.get(), true);

  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();

  DiscountsPageActionController controller(callback, shopping_service_.get());

  // Simulate kShoppingURL has been visited before.
  DiscountsPageActionController::DiscountsShownData* shown_data =
      DiscountsPageActionController::GetOrCreate(shopping_service_.get());
  shown_data->discount_shown_on_domains.insert(kShoppingURLDomain);

  // Before a navigation, the controller should be in an "undecided" state.
  ASSERT_FALSE(controller.WantsExpandedUi());

  EXPECT_CALL(notify_host_callback_, Run()).Times(testing::AtLeast(1));
  EXPECT_CALL(*shopping_service_,
              GetDiscountInfoForUrl(GURL(kShoppingURL), testing::_));
  SetupDiscountResponseForURL(GURL(kShoppingURL));

  // Simulate navigation to |kShoppingURL|
  controller.ResetForNewNavigation(GURL(kShoppingURL));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(controller.WantsExpandedUi());
}

TEST_F(DiscountsPageActionControllerUnittest,
       ShouldNotExpandIcon_ShoppyPageOn_OnVisitedDomain) {
  scoped_feature_list_.InitAndEnableFeatureWithParameters(
      kEnableDiscountInfoApi, {{kDiscountOnShoppyPageParam, "true"}});
  SetUpDiscountEligibilityForAccount(account_checker_.get(), true);
  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();

  DiscountsPageActionController controller(callback, shopping_service_.get());

  // Simulate kShoppingURL has been visited before.
  DiscountsPageActionController::DiscountsShownData* shown_data =
      DiscountsPageActionController::GetOrCreate(shopping_service_.get());
  shown_data->discount_shown_on_domains.insert(kShoppingURLDomain);

  // Before a navigation, the controller should be in an "undecided" state.
  ASSERT_FALSE(controller.WantsExpandedUi());

  EXPECT_CALL(notify_host_callback_, Run()).Times(testing::AtLeast(1));
  EXPECT_CALL(*shopping_service_,
              GetDiscountInfoForUrl(GURL(kShoppingURL), testing::_));
  SetupDiscountResponseForURL(GURL(kShoppingURL));

  // Simulate navigation to |kShoppingURL|
  controller.ResetForNewNavigation(GURL(kShoppingURL));
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(controller.WantsExpandedUi());
}

TEST_F(DiscountsPageActionControllerUnittest, ShouldNotAutoShow) {
  scoped_feature_list_.InitWithFeaturesAndParameters(
      {{kEnableDiscountInfoApi, {}}, GetNoAutoShownBubbleParam()},
      /*disabled_features=*/{});
  SetUpDiscountEligibilityForAccount(account_checker_.get(), true);
  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();

  DiscountsPageActionController controller(callback, shopping_service_.get());
  EXPECT_FALSE(controller.ShouldAutoShowBubble(/*discount_id=*/123,
                                               /*is_merchant_wide=*/false));
  EXPECT_FALSE(controller.ShouldAutoShowBubble(/*discount_id=*/456,
                                               /*is_merchant_wide=*/true));
}

TEST_F(DiscountsPageActionControllerUnittest, ShouldAlwaysAutoShow) {
  scoped_feature_list_.InitWithFeaturesAndParameters(
      {{kEnableDiscountInfoApi, {}}, GetAlwaysAutoShownBubbleParam()},
      /*disabled_features=*/{});
  SetUpDiscountEligibilityForAccount(account_checker_.get(), true);
  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();

  DiscountsPageActionController controller(callback, shopping_service_.get());
  EXPECT_TRUE(controller.ShouldAutoShowBubble(/*discount_id=*/123,
                                              /*is_merchant_wide=*/false));
  EXPECT_TRUE(controller.ShouldAutoShowBubble(/*discount_id=*/456,
                                              /*is_merchant_wide=*/true));
}

TEST_F(DiscountsPageActionControllerUnittest, ShouldAutoShowOnce) {
  constexpr uint64_t discount_id_1 = 123;
  constexpr uint64_t discount_id_2 = 456;
  scoped_feature_list_.InitWithFeaturesAndParameters(
      {{kEnableDiscountInfoApi, {}}, GetAutoShownOnceBubbleParam()},
      /*disabled_features=*/{});
  SetUpDiscountEligibilityForAccount(account_checker_.get(), true);
  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();

  DiscountsPageActionController controller(callback, shopping_service_.get());
  EXPECT_TRUE(controller.ShouldAutoShowBubble(discount_id_1,
                                              /*is_merchant_wide=*/false));
  EXPECT_TRUE(controller.ShouldAutoShowBubble(discount_id_2,
                                              /*is_merchant_wide=*/true));
  // Simulate |discount_id_1| and |discount_id_2| has been shown.
  controller.DiscountsBubbleShown(discount_id_1);
  controller.DiscountsBubbleShown(discount_id_2);

  EXPECT_FALSE(controller.ShouldAutoShowBubble(discount_id_1,
                                               /*is_merchant_wide=*/false));
  EXPECT_FALSE(controller.ShouldAutoShowBubble(discount_id_2,
                                               /*is_merchant_wide=*/true));
}

}  // namespace commerce
