// 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/pdf/infobar/pdf_infobar_controller.h"

#include <memory>

#include "base/test/scoped_feature_list.h"
#include "chrome/browser/infobars/browser_infobar_manager.h"
#include "chrome/browser/infobars/infobar_features.h"
#include "chrome/browser/infobars/infobar_spec.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/prefs/testing_pref_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/unowned_user_data/unowned_user_data_host.h"

namespace pdf::infobar {

class PdfInfoBarControllerTest : public testing::Test {
 protected:
  PdfInfoBarControllerTest()
      : profile_(std::make_unique<TestingProfile>()),
        delegate_(std::make_unique<TestTabStripModelDelegate>()),
        tab_strip_model_(
            std::make_unique<TabStripModel>(delegate(), profile())),
        browser_window_interface_(
            std::make_unique<MockBrowserWindowInterface>()) {
    ON_CALL(*browser_window_interface_, GetTabStripModel())
        .WillByDefault(::testing::Return(tab_strip_model()));
    ON_CALL(*browser_window_interface_, GetProfile())
        .WillByDefault(::testing::Return(profile()));
    ON_CALL(*browser_window_interface_, GetUnownedUserDataHost())
        .WillByDefault(::testing::ReturnRef(unowned_user_data_host_));
    delegate_->SetBrowserWindowInterface(browser_window_interface());
  }

  ~PdfInfoBarControllerTest() override {
    // Break loop so we can deconstruct without dangling pointers.
    delegate_->SetBrowserWindowInterface(nullptr);
  }

  void AddTab() {
    std::unique_ptr<content::WebContents> web_contents =
        content::WebContentsTester::CreateTestWebContents(profile(), nullptr);
    tab_strip_model_->AppendWebContents(std::move(web_contents), true);
  }

  void SetUp() override {
    feature_list_.InitAndEnableFeature(features::kPdfInfoBar);
    PdfInfoBarController::SetHigherPriorityInfoBarShownForTesting(false);
    AddTab();
    infobars::ContentInfoBarManager::CreateForWebContents(
        tab_strip_model_->GetActiveWebContents());

    // `BrowserInfoBarManager::From()` will return `nullptr` (false) if a global
    // `BrowserInfoBarManager` has not yet been instantiated for the testing
    // browser process (e.g., if this is the first test setup in the process, or
    // if it was previously cleared in `TearDown()`). In that case, we
    // instantiate and own one for the duration of the test.
    if (!infobars::BrowserInfoBarManager::From(
            TestingBrowserProcess::GetGlobal())) {
      owned_browser_infobar_manager_ =
          std::make_unique<infobars::BrowserInfoBarManager>(
              TestingBrowserProcess::GetGlobal());
    }
  }

  void TearDown() override { owned_browser_infobar_manager_.reset(); }

  void SetBrowserType(BrowserWindowInterface::Type type) {
    ON_CALL(*browser_window_interface_, GetType)
        .WillByDefault(::testing::Return(type));
  }

  // Calls `MaybeShowInfoBarCallback()` with the given `default_state` and
  // `browser_type`. Returns true if the infobar was shown, false if not.
  bool DidShowInfoBar(BrowserWindowInterface::Type browser_type,
                      shell_integration::DefaultWebClientState default_state) {
    auto* infobar_manager = infobars::ContentInfoBarManager::FromWebContents(
        tab_strip_model()->GetActiveWebContents());
    CHECK(infobar_manager);
    CHECK(infobar_manager->infobars().empty());
    SetBrowserType(browser_type);

    PdfInfoBarController controller{browser_window_interface()};
    controller.MaybeShowInfoBarCallback(default_state);

    // Return true if an infobar was created.
    bool did_show_infobar = !infobar_manager->infobars().empty();
    if (did_show_infobar) {
      // Clear the infobar to ensure `controller` stops observing
      // `infobar_manager` before being destroyed.
      infobar_manager->RemoveAllInfoBars(false);
    }
    return did_show_infobar;
  }

  TestingPrefServiceSimple* local_state() {
    return TestingBrowserProcess::GetGlobal()->GetTestingLocalState();
  }
  TestingProfile* profile() { return profile_.get(); }
  TestTabStripModelDelegate* delegate() { return delegate_.get(); }
  TabStripModel* tab_strip_model() { return tab_strip_model_.get(); }
  MockBrowserWindowInterface* browser_window_interface() {
    return browser_window_interface_.get();
  }

 private:
  // Must be the first member.
  content::BrowserTaskEnvironment task_environment_;

  ui::UnownedUserDataHost unowned_user_data_host_;

  base::test::ScopedFeatureList feature_list_;

  // `ChromeLayoutProvider::Get()` is called when an infobar is created.
  ChromeLayoutProvider layout_provider_;

  content::RenderViewHostTestEnabler rvh_test_enabler_;
  const std::unique_ptr<TestingProfile> profile_;
  const std::unique_ptr<TestTabStripModelDelegate> delegate_;
  const std::unique_ptr<TabStripModel> tab_strip_model_;
  const std::unique_ptr<MockBrowserWindowInterface> browser_window_interface_;
  const tabs::TabModel::PreventFeatureInitializationForTesting prevent_;
  std::unique_ptr<infobars::BrowserInfoBarManager>
      owned_browser_infobar_manager_;
};

// Show the infobar if the feature is enabled, the browser type is normal, and
// Chrome is not the default PDF viewer.
TEST_F(PdfInfoBarControllerTest, MaybeShowInfoBarCallback) {
  EXPECT_TRUE(
      DidShowInfoBar(BrowserWindowInterface::Type::TYPE_NORMAL,
                     shell_integration::DefaultWebClientState::NOT_DEFAULT));
}

// Don't show the infobar if Chrome is already the default PDF viewer.
TEST_F(PdfInfoBarControllerTest, DontShowInfoBarIfChromeIsDefault) {
  EXPECT_FALSE(
      DidShowInfoBar(BrowserWindowInterface::Type::TYPE_NORMAL,
                     shell_integration::DefaultWebClientState::IS_DEFAULT));
}

// Don't show the infobar if another Chrome channel is the default PDF viewer.
TEST_F(PdfInfoBarControllerTest, DontShowInfoBarIfAnotherChromeIsDefault) {
  EXPECT_FALSE(DidShowInfoBar(
      BrowserWindowInterface::Type::TYPE_NORMAL,
      shell_integration::DefaultWebClientState::OTHER_MODE_IS_DEFAULT));
}

// Don't show the infobar if Chrome's PDF viewer is disabled.
TEST_F(PdfInfoBarControllerTest, DontShowInfoBarIfPdfViewerDisabled) {
  profile()->GetPrefs()->SetBoolean(prefs::kPluginsAlwaysOpenPdfExternally,
                                    true);
  EXPECT_FALSE(
      DidShowInfoBar(BrowserWindowInterface::Type::TYPE_NORMAL,
                     shell_integration::DefaultWebClientState::NOT_DEFAULT));
}

// Don't show the infobar if default apps are managed by a policy.
TEST_F(PdfInfoBarControllerTest, DontShowInfoBarIfDefaultIsPolicyControlled) {
  local_state()->SetManagedPref(prefs::kDefaultBrowserSettingEnabled,
                                base::Value(true));
  EXPECT_FALSE(
      DidShowInfoBar(BrowserWindowInterface::Type::TYPE_NORMAL,
                     shell_integration::DefaultWebClientState::NOT_DEFAULT));
}

TEST_F(PdfInfoBarControllerTest, DontShowInfoBarIfDefaultBrowserPromptShown) {
  PdfInfoBarController::SetHigherPriorityInfoBarShownForTesting(true);
  EXPECT_FALSE(
      DidShowInfoBar(BrowserWindowInterface::Type::TYPE_NORMAL,
                     shell_integration::DefaultWebClientState::NOT_DEFAULT));
}

TEST_F(PdfInfoBarControllerTest, MaybeShowInfoBarCallbackMigrated) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeatureWithParameters(
      infobars::kCentralizedInfoBarFramework, {{"kMigratedPdf", "true"}});

  auto* browser_infobar_manager =
      infobars::BrowserInfoBarManager::From(TestingBrowserProcess::GetGlobal());
  ASSERT_TRUE(browser_infobar_manager);

  auto spec = infobars::InfoBarSpec::Builder(
                  infobars::InfoBarDelegate::PDF_INFOBAR_DELEGATE)
                  .SetMessageText(u"Modern PDF Infobar")
                  .SetScope(infobars::InfoBarScope::kTab)
                  .Build();
  browser_infobar_manager->Register(std::move(spec));

  EXPECT_TRUE(
      DidShowInfoBar(BrowserWindowInterface::Type::TYPE_NORMAL,
                     shell_integration::DefaultWebClientState::NOT_DEFAULT));
}

}  // namespace pdf::infobar
