// Copyright 2018 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/badging/badge_manager.h"

#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "chrome/browser/badging/badge_manager_delegate.h"
#include "chrome/browser/badging/badge_manager_factory.h"
#include "chrome/browser/badging/test_badge_manager_delegate.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/test/base/testing_profile.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_id.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using badging::BadgeManager;
using badging::BadgeManagerDelegate;

namespace {

typedef std::pair<GURL, std::optional<int>> SetBadgeAction;

constexpr uint64_t kBadgeContents = 1;
const webapps::AppId kAppId = "1";

}  // namespace

namespace badging {

class BadgeManagerUnittest : public ::testing::Test {
 public:
  BadgeManagerUnittest() = default;

  BadgeManagerUnittest(const BadgeManagerUnittest&) = delete;
  BadgeManagerUnittest& operator=(const BadgeManagerUnittest&) = delete;

  ~BadgeManagerUnittest() override = default;

  void SetUp() override {
    TestingProfile::Builder builder;
    profile_ = builder.Build();

    provider_ = web_app::FakeWebAppProvider::Get(profile());
    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());

    // Delegate lifetime is managed by BadgeManager
    auto owned_delegate = std::make_unique<TestBadgeManagerDelegate>(
        profile_.get(), &badge_manager());
    delegate_ = owned_delegate.get();
    badge_manager().SetDelegate(std::move(owned_delegate));
  }

  void TearDown() override {
    // Clear raw_ptrs before `profile_` is reset to avoid a dangling pointer.
    delegate_ = nullptr;
    provider_ = nullptr;
    profile_.reset();
  }

  TestBadgeManagerDelegate* delegate() { return delegate_; }

  void set_delegate(TestBadgeManagerDelegate* delegate) {
    delegate_ = delegate;
  }

  BadgeManager& badge_manager() const {
    return *BadgeManagerFactory::GetForProfile(profile());
  }

  Profile* profile() const { return profile_.get(); }

  web_app::WebAppProvider& provider() { return *provider_; }

 private:
  raw_ptr<web_app::FakeWebAppProvider> provider_;

  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestingProfile> profile_;

  raw_ptr<TestBadgeManagerDelegate> delegate_;
};

TEST_F(BadgeManagerUnittest, SetFlagBadgeForApp) {
  ukm::TestUkmRecorder test_recorder;
  badge_manager().SetBadgeForTesting(kAppId, std::nullopt, &test_recorder);

  auto entries =
      test_recorder.GetEntriesByName(ukm::builders::Badging::kEntryName);
  ASSERT_EQ(entries.size(), 1u);
  test_recorder.ExpectEntryMetric(
      entries[0], ukm::builders::Badging::kUpdateAppBadgeName, kSetFlagBadge);

  EXPECT_EQ(1UL, delegate()->set_badges().size());
  EXPECT_EQ(kAppId, delegate()->set_badges().front().first);
  EXPECT_EQ(std::nullopt, delegate()->set_badges().front().second);
}

TEST_F(BadgeManagerUnittest, SetBadgeForApp) {
  ukm::TestUkmRecorder test_recorder;
  badge_manager().SetBadgeForTesting(kAppId, std::make_optional(kBadgeContents),
                                     &test_recorder);
  auto entries =
      test_recorder.GetEntriesByName(ukm::builders::Badging::kEntryName);
  ASSERT_EQ(entries.size(), 1u);
  test_recorder.ExpectEntryMetric(entries[0],
                                  ukm::builders::Badging::kUpdateAppBadgeName,
                                  kSetNumericBadge);
  EXPECT_EQ(1UL, delegate()->set_badges().size());
  EXPECT_EQ(kAppId, delegate()->set_badges().front().first);
  EXPECT_EQ(kBadgeContents, delegate()->set_badges().front().second);
}

TEST_F(BadgeManagerUnittest, SetBadgeForMultipleApps) {
  const webapps::AppId kOtherAppId = "2";
  constexpr uint64_t kOtherContents = 2;

  std::vector<webapps::AppId> updated_apps;
  web_app::WebAppTestRegistryObserverAdapter observer(
      &provider().registrar_unsafe());
  observer.SetWebAppLastBadgingTimeChangedDelegate(base::BindLambdaForTesting(
      [&updated_apps](const webapps::AppId& app_id, const base::Time& time) {
        updated_apps.push_back(app_id);
      }));

  badge_manager().SetBadgeForTesting(kAppId, std::make_optional(kBadgeContents),
                                     ukm::TestUkmRecorder::Get());
  badge_manager().SetBadgeForTesting(kOtherAppId,
                                     std::make_optional(kOtherContents),
                                     ukm::TestUkmRecorder::Get());

  EXPECT_EQ(2UL, delegate()->set_badges().size());

  EXPECT_EQ(kAppId, delegate()->set_badges()[0].first);
  EXPECT_EQ(kBadgeContents, delegate()->set_badges()[0].second);

  EXPECT_EQ(kOtherAppId, delegate()->set_badges()[1].first);
  EXPECT_EQ(kOtherContents, delegate()->set_badges()[1].second);

  EXPECT_EQ(2UL, updated_apps.size());
  EXPECT_EQ(kAppId, updated_apps[0]);
  EXPECT_EQ(kOtherAppId, updated_apps[1]);
}

TEST_F(BadgeManagerUnittest, SetBadgeForAppAfterClear) {
  badge_manager().SetBadgeForTesting(kAppId, std::make_optional(kBadgeContents),
                                     ukm::TestUkmRecorder::Get());
  badge_manager().ClearBadgeForTesting(kAppId, ukm::TestUkmRecorder::Get());
  badge_manager().SetBadgeForTesting(kAppId, std::make_optional(kBadgeContents),
                                     ukm::TestUkmRecorder::Get());

  EXPECT_EQ(2UL, delegate()->set_badges().size());

  EXPECT_EQ(kAppId, delegate()->set_badges()[0].first);
  EXPECT_EQ(kBadgeContents, delegate()->set_badges()[0].second);

  EXPECT_EQ(kAppId, delegate()->set_badges()[1].first);
  EXPECT_EQ(kBadgeContents, delegate()->set_badges()[1].second);
}

TEST_F(BadgeManagerUnittest, ClearBadgeForBadgedApp) {
  ukm::TestUkmRecorder test_recorder;

  badge_manager().SetBadgeForTesting(kAppId, std::make_optional(kBadgeContents),
                                     ukm::TestUkmRecorder::Get());
  badge_manager().ClearBadgeForTesting(kAppId, &test_recorder);
  auto entries =
      test_recorder.GetEntriesByName(ukm::builders::Badging::kEntryName);
  ASSERT_EQ(entries.size(), 1u);
  test_recorder.ExpectEntryMetric(
      entries[0], ukm::builders::Badging::kUpdateAppBadgeName, kClearBadge);
  EXPECT_EQ(1UL, delegate()->cleared_badges().size());
  EXPECT_EQ(kAppId, delegate()->cleared_badges().front());
}

TEST_F(BadgeManagerUnittest, BadgingMultipleProfiles) {
  std::unique_ptr<Profile> other_profile = std::make_unique<TestingProfile>();
  web_app::FakeWebAppProvider* new_provider =
      web_app::FakeWebAppProvider::Get(other_profile.get());
  web_app::test::AwaitStartWebAppProviderAndSubsystems(other_profile.get());

  BadgeManager* other_badge_manager =
      BadgeManagerFactory::GetForProfile(other_profile.get());
  ASSERT_TRUE(other_badge_manager);

  auto owned_other_delegate = std::make_unique<TestBadgeManagerDelegate>(
      other_profile.get(), other_badge_manager);
  auto* other_delegate = owned_other_delegate.get();
  other_badge_manager->SetDelegate(std::move(owned_other_delegate));

  std::vector<webapps::AppId> updated_apps;
  std::vector<webapps::AppId> other_updated_apps;
  web_app::WebAppTestRegistryObserverAdapter other_observer(
      &new_provider->registrar_unsafe());
  other_observer.SetWebAppLastBadgingTimeChangedDelegate(
      base::BindLambdaForTesting(
          [&other_updated_apps](const webapps::AppId& app_id,
                                const base::Time& time) {
            other_updated_apps.push_back(app_id);
          }));
  web_app::WebAppTestRegistryObserverAdapter observer(
      &provider().registrar_unsafe());
  observer.SetWebAppLastBadgingTimeChangedDelegate(base::BindLambdaForTesting(
      [&updated_apps](const webapps::AppId& app_id, const base::Time& time) {
        updated_apps.push_back(app_id);
      }));

  other_badge_manager->SetBadgeForTesting(kAppId, std::nullopt,
                                          ukm::TestUkmRecorder::Get());
  other_badge_manager->SetBadgeForTesting(
      kAppId, std::make_optional(kBadgeContents), ukm::TestUkmRecorder::Get());
  other_badge_manager->SetBadgeForTesting(kAppId, std::nullopt,
                                          ukm::TestUkmRecorder::Get());
  other_badge_manager->ClearBadgeForTesting(kAppId,
                                            ukm::TestUkmRecorder::Get());

  badge_manager().ClearBadgeForTesting(kAppId, ukm::TestUkmRecorder::Get());

  EXPECT_EQ(3UL, other_delegate->set_badges().size());
  EXPECT_EQ(0UL, delegate()->set_badges().size());

  EXPECT_EQ(1UL, other_delegate->cleared_badges().size());
  EXPECT_EQ(1UL, delegate()->cleared_badges().size());

  EXPECT_EQ(kAppId, other_delegate->set_badges().back().first);
  EXPECT_EQ(std::nullopt, other_delegate->set_badges().back().second);

  EXPECT_EQ(1UL, updated_apps.size());
  EXPECT_EQ(kAppId, updated_apps[0]);

  EXPECT_FALSE(other_updated_apps.empty());
  EXPECT_EQ(kAppId, other_updated_apps[0]);
}

// Tests methods which call into the badge manager delegate do not crash when
// the delegate is unset.
TEST_F(BadgeManagerUnittest, BadgingWithNoDelegateDoesNotCrash) {
  // Set the delegate to nullptr to avoid a dangling pointer.
  set_delegate(nullptr);
  badge_manager().SetDelegate(nullptr);

  badge_manager().SetBadgeForTesting(kAppId, std::nullopt,
                                     ukm::TestUkmRecorder::Get());
  badge_manager().SetBadgeForTesting(kAppId, std::make_optional(kBadgeContents),
                                     ukm::TestUkmRecorder::Get());
  badge_manager().ClearBadgeForTesting(kAppId, ukm::TestUkmRecorder::Get());
}

}  // namespace badging
