// Copyright 2022 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/profiles/profile_customization_synced_theme_waiter.h"

#include <memory>

#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chrome/browser/themes/theme_helper.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_syncable_service.h"
#include "chrome/test/base/testing_profile.h"
#include "components/sync/test/test_sync_service.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkColor.h"

class FakeThemeService : public ThemeService {
 public:
  explicit FakeThemeService(const ThemeHelper& theme_helper)
      : ThemeService(nullptr, theme_helper) {}

  void SetThemeSyncableService(ThemeSyncableService* theme_syncable_service) {
    theme_syncable_service_ = theme_syncable_service;
    set_ready();
  }

  // ThemeService:
  void DoSetTheme(const extensions::Extension* extension,
                  bool suppress_infobar) override {
    using_default_theme_ = false;
    color_ = 0;
    NotifyThemeChanged();
  }

  void BuildAutogeneratedThemeFromColor(SkColor color) override {
    color_ = color;
    using_default_theme_ = false;
    NotifyThemeChanged();
  }

  void UseTheme(ui::SystemTheme system_theme) override {
    if (system_theme == ui::SystemTheme::kDefault) {
      using_default_theme_ = true;
      color_ = 0;
    }
    NotifyThemeChanged();
  }

  bool UsingDefaultTheme() const override { return using_default_theme_; }

  SkColor GetAutogeneratedThemeColor() const override { return color_; }

  ThemeSyncableService* GetThemeSyncableService() const override {
    return theme_syncable_service_;
  }

 private:
  raw_ptr<ThemeSyncableService> theme_syncable_service_ = nullptr;
  bool using_default_theme_ = true;
  SkColor color_ = 0;
};

class ProfileCustomizationSyncedThemeWaiterTest : public testing::Test {
 public:
  ProfileCustomizationSyncedThemeWaiterTest()
      : fake_theme_service_(theme_helper_),
        theme_syncable_service_(&profile_, &fake_theme_service_) {}

  void SetUp() override {
    fake_theme_service_.SetThemeSyncableService(&theme_syncable_service_);
  }

  void WaitForSyncedTheme(
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>
          callback) {
    waiter_ = std::make_unique<ProfileCustomizationSyncedThemeWaiter>(
        &test_sync_service_, &fake_theme_service_, std::move(callback));
    waiter_->Run();
  }

  void SetSyncedProfileColor() {
    constexpr SkColor kSyncedProfileColor = SK_ColorMAGENTA;
    fake_theme_service_.BuildAutogeneratedThemeFromColor(kSyncedProfileColor);
  }

  void SetSyncedProfileTheme() {
    fake_theme_service_.DoSetTheme(nullptr, false);
  }

  void DeleteWaiter() { waiter_.reset(); }

  void NotifyOnSyncStarted(bool waiting_for_extension_installation = false) {
    theme_syncable_service_.NotifyOnSyncStartedForTesting(
        waiting_for_extension_installation
            ? ThemeSyncableService::ThemeSyncState::
                  kWaitingForExtensionInstallation
            : ThemeSyncableService::ThemeSyncState::kApplied);
  }

  void FastForwardBy(base::TimeDelta delta) {
    task_environment_.FastForwardBy(delta);
  }

  syncer::TestSyncService* test_sync_service() { return &test_sync_service_; }

 private:
  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  std::unique_ptr<ProfileCustomizationSyncedThemeWaiter> waiter_;

  TestingProfile profile_;
  syncer::TestSyncService test_sync_service_;
  FakeThemeService fake_theme_service_;
  ThemeSyncableService theme_syncable_service_;
  ThemeHelper theme_helper_;
};

TEST_F(ProfileCustomizationSyncedThemeWaiterTest, SyncGetsDefaultTheme) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(
      callback,
      Run(ProfileCustomizationSyncedThemeWaiter::Outcome::kSyncSuccess));

  WaitForSyncedTheme(callback.Get());
  NotifyOnSyncStarted();
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest,
       SyncGetsDefaultTheme_BeforeWaitStarted) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(
      callback,
      Run(ProfileCustomizationSyncedThemeWaiter::Outcome::kSyncSuccess));

  NotifyOnSyncStarted();
  WaitForSyncedTheme(callback.Get());
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest, SyncGetsCustomColor) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(
      callback,
      Run(ProfileCustomizationSyncedThemeWaiter::Outcome::kSyncSuccess));

  WaitForSyncedTheme(callback.Get());
  SetSyncedProfileColor();
  NotifyOnSyncStarted();
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest, SyncGetsCustomTheme) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(
      callback,
      Run(ProfileCustomizationSyncedThemeWaiter::Outcome::kSyncSuccess));

  WaitForSyncedTheme(callback.Get());
  SetSyncedProfileTheme();
  NotifyOnSyncStarted();
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest,
       SyncGetsCustomThemeToInstall) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;

  WaitForSyncedTheme(callback.Get());
  NotifyOnSyncStarted(/*waiting_for_extension_installation=*/true);

  // The callback should not have run before the theme is set.
  EXPECT_CALL(
      callback,
      Run(ProfileCustomizationSyncedThemeWaiter::Outcome::kSyncSuccess));
  SetSyncedProfileTheme();
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest, SyncDisabled) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(
      callback,
      Run(ProfileCustomizationSyncedThemeWaiter::Outcome::kSyncCannotStart));

  test_sync_service()->SetAllowedByEnterprisePolicy(false);
  WaitForSyncedTheme(callback.Get());
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest,
       SyncDisabled_AfterWaitStarted) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(
      callback,
      Run(ProfileCustomizationSyncedThemeWaiter::Outcome::kSyncCannotStart));

  WaitForSyncedTheme(callback.Get());
  test_sync_service()->SetAllowedByEnterprisePolicy(false);
  test_sync_service()->FireStateChanged();
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest, SyncHasCustomPasshrase) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(callback, Run(ProfileCustomizationSyncedThemeWaiter::Outcome::
                                kSyncPassphraseRequired));

  test_sync_service()->SetPassphraseRequired();
  WaitForSyncedTheme(callback.Get());
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest,
       SyncHasCustomPasshrase_AfterWaitStarted) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(callback, Run(ProfileCustomizationSyncedThemeWaiter::Outcome::
                                kSyncPassphraseRequired));

  WaitForSyncedTheme(callback.Get());
  test_sync_service()->SetPassphraseRequired();
  test_sync_service()->FireStateChanged();
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest, Timeout) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(callback,
              Run(ProfileCustomizationSyncedThemeWaiter::Outcome::kTimeout));

  WaitForSyncedTheme(callback.Get());
  FastForwardBy(base::Seconds(4));
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest,
       SyncGetsDefaultTheme_TimeoutIgnored) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  EXPECT_CALL(
      callback,
      Run(ProfileCustomizationSyncedThemeWaiter::Outcome::kSyncSuccess));

  WaitForSyncedTheme(callback.Get());
  NotifyOnSyncStarted();
  // Verify that nothing happens after timeout expires.
  FastForwardBy(base::Seconds(4));
}

TEST_F(ProfileCustomizationSyncedThemeWaiterTest, WaiterDeletedBeforeDone) {
  base::MockCallback<
      base::OnceCallback<void(ProfileCustomizationSyncedThemeWaiter::Outcome)>>
      callback;
  WaitForSyncedTheme(callback.Get());
  DeleteWaiter();
  // Verify that nothing happens after timeout expires.
  FastForwardBy(base::Seconds(4));
}
