// Copyright 2025 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/webui/customize_buttons/customize_buttons_handler.h"

#include <optional>

#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_list/tab_list_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/customize_chrome/side_panel_controller.h"
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "chrome/browser/ui/webui/customize_buttons/customize_buttons.mojom.h"
#include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_section.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/platform_browser_test.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/prefs/pref_service.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_web_ui.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/unowned_user_data/scoped_unowned_user_data.h"

class MockFeaturePromoHelper : public NewTabPageFeaturePromoHelper {
 public:
  MOCK_METHOD(void,
              RecordPromoFeatureUsageAndClosePromo,
              (const base::Feature& feature, content::WebContents*),
              (override));
  MOCK_METHOD(bool,
              IsSigninModalDialogOpen,
              (content::WebContents*),
              (override));

  ~MockFeaturePromoHelper() override = default;
};

class MockCustomizeButtonsDocument
    : public customize_buttons::mojom::CustomizeButtonsDocument {
 public:
  MockCustomizeButtonsDocument() = default;
  ~MockCustomizeButtonsDocument() override = default;

  mojo::PendingRemote<customize_buttons::mojom::CustomizeButtonsDocument>
  BindAndGetRemote() {
    DCHECK(!receiver_.is_bound());
    return receiver_.BindNewPipeAndPassRemote();
  }
  mojo::Receiver<customize_buttons::mojom::CustomizeButtonsDocument> receiver_{
      this};

  void FlushForTesting() { receiver_.FlushForTesting(); }

  MOCK_METHOD(void, SetCustomizeChromeSidePanelVisibility, (bool));
};

class MockCustomizeChromeTabHelper
    : public customize_chrome::SidePanelController {
 public:
  explicit MockCustomizeChromeTabHelper(ui::UnownedUserDataHost& host)
      : scoped_unowned_user_data_(host, *this) {}
  ~MockCustomizeChromeTabHelper() override = default;

  MOCK_METHOD(bool, IsCustomizeChromeEntryAvailable, (), (const, override));
  MOCK_METHOD(bool, IsCustomizeChromeEntryShowing, (), (const, override));
  MOCK_METHOD(void,
              SetEntryChangedCallback,
              (customize_chrome::SidePanelController::StateChangedCallBack),
              (override));
  MOCK_METHOD(void,
              OpenSidePanel,
              (SidePanelOpenTrigger, std::optional<CustomizeChromeSection>),
              (override));
  MOCK_METHOD(void, CloseSidePanel, (), (override));

 private:
  ui::ScopedUnownedUserData<customize_chrome::SidePanelController>
      scoped_unowned_user_data_;
};

class CustomizeButtonsHandlerBrowserTestBase : public PlatformBrowserTest {
 public:
  void SetUpOnMainThread() override {
    web_ui_ = std::make_unique<content::TestWebUI>();
    web_ui_->set_web_contents(GetActiveTab()->GetContents());

    tabs::TabInterface* tab = GetActiveTab();
    tabs::TabFeatures* tab_features = tab->GetTabFeatures();
    tab_features->SetCustomizeChromeSidePanelControllerForTesting(nullptr);
    auto mock_controller_ptr = std::make_unique<MockCustomizeChromeTabHelper>(
        tab->GetUnownedUserDataHost());
    mock_controller_ = mock_controller_ptr.get();
    tab_features->SetCustomizeChromeSidePanelControllerForTesting(
        std::move(mock_controller_ptr));
  }

  void CreateHandler(bool set_tab_interface) {
    tabs::TabInterface* tab = nullptr;
    if (set_tab_interface) {
      tab = GetActiveTab();
    }

    auto promo_helper_ptr = std::make_unique<MockFeaturePromoHelper>();
    promo_helper_ = promo_helper_ptr.get();

    handler_ = std::make_unique<CustomizeButtonsHandler>(
        mojo::PendingReceiver<
            customize_buttons::mojom::CustomizeButtonsHandler>(),
        doc_.BindAndGetRemote(), web_ui_.get(), tab,
        std::move(promo_helper_ptr));
  }

  Profile* profile() { return GetBrowserWindowInterface()->GetProfile(); }

  tabs::TabInterface* GetActiveTab() {
    return GetTabListInterface()->GetActiveTab();
  }

  void TearDownOnMainThread() override {
    promo_helper_ = nullptr;
    handler_.reset();
    mock_controller_ = nullptr;
    PlatformBrowserTest::TearDownOnMainThread();
  }

  MockFeaturePromoHelper* GetMockFeaturePromoHelper() {
    return promo_helper_.get();
  }

  MockCustomizeButtonsDocument doc_;
  std::unique_ptr<content::TestWebUI> web_ui_;
  raw_ptr<MockFeaturePromoHelper> promo_helper_;
  std::unique_ptr<CustomizeButtonsHandler> handler_;
  raw_ptr<MockCustomizeChromeTabHelper> mock_controller_;
};

class CustomizeButtonsHandlerBrowserTest
    : public CustomizeButtonsHandlerBrowserTestBase,
      public testing::WithParamInterface<bool> {
 public:
  void SetUpOnMainThread() override {
    CustomizeButtonsHandlerBrowserTestBase::SetUpOnMainThread();
    CreateHandler(/*set_tab_interface=*/GetParam());
  }
};

INSTANTIATE_TEST_SUITE_P(All,
                         CustomizeButtonsHandlerBrowserTest,
                         // Run the tests with CustomizeButtonsHandler created
                         // with and without a TabInterface.
                         testing::Bool());

IN_PROC_BROWSER_TEST_P(CustomizeButtonsHandlerBrowserTest, OpenSidePanelTwice) {
  SidePanelOpenTrigger trigger;
  std::optional<CustomizeChromeSection> section;
  bool visible;

  EXPECT_CALL(*mock_controller_.get(), OpenSidePanel)
      .Times(2)
      .WillRepeatedly(testing::DoAll(testing::SaveArg<0>(&trigger),
                                     testing::SaveArg<1>(&section)));
  EXPECT_CALL(doc_, SetCustomizeChromeSidePanelVisibility)
      .Times(2)
      .WillRepeatedly(testing::SaveArg<0>(&visible));
#if !BUILDFLAG(IS_ANDROID)
  content::WebContents* web_contents = GetActiveTab()->GetContents();
  EXPECT_CALL(
      *GetMockFeaturePromoHelper(),
      RecordPromoFeatureUsageAndClosePromo(
          testing::Ref(
              feature_engagement::kIPHDesktopCustomizeChromeExperimentFeature),
          web_contents))
      .Times(2);
  EXPECT_CALL(
      *GetMockFeaturePromoHelper(),
      RecordPromoFeatureUsageAndClosePromo(
          testing::Ref(
              feature_engagement::kIPHDesktopCustomizeChromeAutoOpenFeature),
          web_contents))
      .Times(2);
#endif

  handler_->SetCustomizeChromeSidePanelVisible(
      /*visible=*/true, CustomizeChromeSection::kUnspecified,
      customize_buttons::mojom::SidePanelOpenTrigger::kNewTabFooter);
  doc_.FlushForTesting();

  EXPECT_EQ(SidePanelOpenTrigger::kNewTabFooter, trigger);
  EXPECT_EQ(CustomizeChromeSection::kUnspecified, section);
  EXPECT_TRUE(visible);

  handler_->SetCustomizeChromeSidePanelVisible(
      /*visible=*/true, CustomizeChromeSection::kAppearance,
      customize_buttons::mojom::SidePanelOpenTrigger::kNewTabPage);
  doc_.FlushForTesting();

  EXPECT_EQ(SidePanelOpenTrigger::kNewTabPage, trigger);
  EXPECT_EQ(CustomizeChromeSection::kAppearance, section);
  EXPECT_TRUE(visible);
}

IN_PROC_BROWSER_TEST_P(CustomizeButtonsHandlerBrowserTest, CloseSidePanel) {
  ON_CALL(*mock_controller_.get(), IsCustomizeChromeEntryShowing())
      .WillByDefault(testing::Return(true));

  EXPECT_CALL(*mock_controller_.get(), CloseSidePanel).Times(1);
  EXPECT_CALL(doc_, SetCustomizeChromeSidePanelVisibility)
      .WillOnce([](bool visible) { EXPECT_FALSE(visible); });
  EXPECT_CALL(*GetMockFeaturePromoHelper(),
              RecordPromoFeatureUsageAndClosePromo)
      .Times(0);

  handler_->SetCustomizeChromeSidePanelVisible(
      /*visible=*/false, CustomizeChromeSection::kModules,
      customize_buttons::mojom::SidePanelOpenTrigger::kNewTabPage);
  doc_.FlushForTesting();
}

IN_PROC_BROWSER_TEST_P(CustomizeButtonsHandlerBrowserTest,
                       IncrementCustomizeChromeButtonOpenCount) {
  EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                prefs::kNtpCustomizeChromeButtonOpenCount),
            0);

  handler_->IncrementCustomizeChromeButtonOpenCount();

  EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                prefs::kNtpCustomizeChromeButtonOpenCount),
            1);

  handler_->IncrementCustomizeChromeButtonOpenCount();

  EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                prefs::kNtpCustomizeChromeButtonOpenCount),
            2);
}

IN_PROC_BROWSER_TEST_P(CustomizeButtonsHandlerBrowserTest,
                       IncrementWallpaperSearchButtonShownCount) {
  EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                prefs::kNtpWallpaperSearchButtonShownCount),
            0);
  handler_->IncrementWallpaperSearchButtonShownCount();

  EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                prefs::kNtpWallpaperSearchButtonShownCount),
            1);
}

class CustomizeButtonsHandlerTriggerParamTest
    : public CustomizeButtonsHandlerBrowserTestBase,
      public testing::WithParamInterface<
          customize_buttons::mojom::SidePanelOpenTrigger> {
 public:
  void SetUpOnMainThread() override {
    CustomizeButtonsHandlerBrowserTestBase::SetUpOnMainThread();
    CreateHandler(/*set_tab_interface=*/false);
  }

 protected:
  customize_buttons::mojom::SidePanelOpenTrigger trigger_param() const {
    return GetParam();
  }

  SidePanelOpenTrigger GetExpectedTrigger(
      customize_buttons::mojom::SidePanelOpenTrigger trigger) {
    switch (trigger) {
      case customize_buttons::mojom::SidePanelOpenTrigger::kNewTabPage:
        return SidePanelOpenTrigger::kNewTabPage;
      case customize_buttons::mojom::SidePanelOpenTrigger::kNewTabFooter:
        return SidePanelOpenTrigger::kNewTabFooter;
    }
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    CustomizeButtonsHandlerTriggerParamTest,
    testing::Values(
        customize_buttons::mojom::SidePanelOpenTrigger::kNewTabPage,
        customize_buttons::mojom::SidePanelOpenTrigger::kNewTabFooter));

IN_PROC_BROWSER_TEST_P(CustomizeButtonsHandlerTriggerParamTest, OpenSidePanel) {
  std::optional<CustomizeChromeSection> section;
  SidePanelOpenTrigger trigger;

  EXPECT_CALL(*mock_controller_.get(), OpenSidePanel)
      .Times(1)
      .WillOnce(testing::DoAll(testing::SaveArg<0>(&trigger),
                               testing::SaveArg<1>(&section)));
#if !BUILDFLAG(IS_ANDROID)
  content::WebContents* web_contents = GetActiveTab()->GetContents();
  EXPECT_CALL(
      *GetMockFeaturePromoHelper(),
      RecordPromoFeatureUsageAndClosePromo(
          testing::Ref(
              feature_engagement::kIPHDesktopCustomizeChromeExperimentFeature),
          web_contents))
      .Times(1);
  EXPECT_CALL(
      *GetMockFeaturePromoHelper(),
      RecordPromoFeatureUsageAndClosePromo(
          testing::Ref(
              feature_engagement::kIPHDesktopCustomizeChromeAutoOpenFeature),
          web_contents))
      .Times(1);
#endif

  handler_->SetCustomizeChromeSidePanelVisible(
      /*visible=*/true, CustomizeChromeSection::kUnspecified, trigger_param());

  EXPECT_EQ(trigger, GetExpectedTrigger(trigger_param()));
}
