// Copyright 2023 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/chromeos/enterprise/cloud_storage/one_drive_pref_observer.h"

#include "ash/constants/ash_extension_constants.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/web_app_id_constants.h"
#include "base/check_deref.h"
#include "base/check_is_test.h"
#include "base/functional/bind.h"
#include "base/values.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/chromeos/enterprise/cloud_storage/policy_utils.h"
#include "chrome/browser/chromeos/extensions/odfs_config_private/odfs_config_private_api.h"
#include "chrome/browser/chromeos/upload_office_to_cloud/upload_office_to_cloud.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_selections.h"
#include "chrome/common/extensions/api/odfs_config_private.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "content/public/browser/browser_context.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/extension_event_histogram_value.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_system_provider.h"
#include "extensions/browser/extensions_browser_client.h"

using extensions::api::odfs_config_private::AccountRestrictionsInfo;
using extensions::api::odfs_config_private::Mount;
using extensions::api::odfs_config_private::MountInfo;

namespace chromeos::cloud_storage {

namespace {

// This class is responsible for watching the prefs for a particular profile.
class OneDrivePrefObserver : public KeyedService,
                             public apps::AppRegistryCache::Observer {
 public:
  ~OneDrivePrefObserver() override;

  static std::unique_ptr<OneDrivePrefObserver> Create(Profile* profile);

  // KeyedService:
  void Shutdown() override;

 private:
  explicit OneDrivePrefObserver(Profile* profile);

  // Sets up watchers.
  void Init();

  // apps::AppRegistryCache::Observer:
  void OnAppUpdate(const apps::AppUpdate& update) override;
  void OnAppRegistryCacheWillBeDestroyed(
      apps::AppRegistryCache* cache) override;

  void MaybeSetSupportedLinksPreference();

  // Serves as callback for pref changes.
  void OnMicrosoftOneDriveMountPrefChanged();
  void OnMicrosoftOneDriveAccountRestrictionsPrefChanged();

  void BroadcastModeChanged(Mount mode);
  void BroadcastAccountRestrictionsChanged(base::ListValue restrictions);

  void MaybeUninstallOdfsExtension(Mount mode);

  // Keyed services are shut down from the embedder's destruction of the profile
  // and this pointer is reset in `ShutDown`. Therefore it is safe to use this
  // raw pointer.
  raw_ptr<Profile> profile_ = nullptr;

  // This keyed service depends on the EventRouter keyed service and will be
  // destroyed before the EventRouter. Therefore it is safe to use this raw
  // pointer.
  raw_ptr<extensions::EventRouter> event_router_ = nullptr;

  // The registrar used to watch prefs changes.
  std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;

  base::ScopedObservation<apps::AppRegistryCache,
                          apps::AppRegistryCache::Observer>
      app_registry_cache_observer_{this};
};

OneDrivePrefObserver::OneDrivePrefObserver(Profile* profile)
    : profile_(profile),
      event_router_(extensions::EventRouter::Get(profile)),
      pref_change_registrar_(std::make_unique<PrefChangeRegistrar>()) {}

OneDrivePrefObserver::~OneDrivePrefObserver() = default;

std::unique_ptr<OneDrivePrefObserver> OneDrivePrefObserver::Create(
    Profile* profile) {
  auto watcher = base::WrapUnique(new OneDrivePrefObserver(profile));
  watcher->Init();
  return watcher;
}

void OneDrivePrefObserver::Init() {
  pref_change_registrar_->Init(profile_->GetPrefs());
  pref_change_registrar_->Add(
      ash::prefs::kMicrosoftOneDriveMount,
      base::BindRepeating(
          &OneDrivePrefObserver::OnMicrosoftOneDriveMountPrefChanged,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      ash::prefs::kMicrosoftOneDriveAccountRestrictions,
      base::BindRepeating(&OneDrivePrefObserver::
                              OnMicrosoftOneDriveAccountRestrictionsPrefChanged,
                          base::Unretained(this)));
  OnMicrosoftOneDriveMountPrefChanged();
  OnMicrosoftOneDriveAccountRestrictionsPrefChanged();

  if (apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile_)) {
    apps::AppRegistryCache& cache =
        apps::AppServiceProxyFactory::GetForProfile(profile_)
            ->AppRegistryCache();
    app_registry_cache_observer_.Observe(&cache);

    // Check if the app is already ready on startup.
    cache.ForOneApp(ash::kMicrosoft365AppId,
                    [this](const apps::AppUpdate& update) {
                      if (update.Readiness() == apps::Readiness::kReady) {
                        MaybeSetSupportedLinksPreference();
                      }
                    });
  }
}

void OneDrivePrefObserver::Shutdown() {
  pref_change_registrar_.reset();
  event_router_ = nullptr;
  profile_ = nullptr;
}

void OneDrivePrefObserver::OnMicrosoftOneDriveMountPrefChanged() {
  const Mount mount = GetMicrosoftOneDriveMount(profile_);
  BroadcastModeChanged(mount);
  MaybeUninstallOdfsExtension(mount);
}

void OneDrivePrefObserver::OnMicrosoftOneDriveAccountRestrictionsPrefChanged() {
  BroadcastAccountRestrictionsChanged(
      GetMicrosoftOneDriveAccountRestrictions(profile_.get()));
}

void OneDrivePrefObserver::MaybeUninstallOdfsExtension(Mount mount) {
  if (cloud_upload::IsMicrosoftOfficeOneDriveIntegrationAllowed(profile_) ||
      !CHECK_DEREF(extensions::ExtensionRegistry::Get(profile_))
           .enabled_extensions()
           .GetByID(extension_misc::kODFSExtensionId)) {
    return;
  }
  CHECK_DEREF(extensions::ExtensionRegistrar::Get(profile_))
      .RemoveComponentExtension(extension_misc::kODFSExtensionId);
}

}  // namespace

OneDrivePrefObserverFactory* OneDrivePrefObserverFactory::GetInstance() {
  static base::NoDestructor<OneDrivePrefObserverFactory> instance;
  return instance.get();
}

OneDrivePrefObserverFactory::OneDrivePrefObserverFactory()
    : ProfileKeyedServiceFactory(
          "OneDrivePrefObserverFactory",
          ProfileSelections::Builder()
              .WithRegular(ProfileSelection::kOriginalOnly)
              .Build()) {
  DependsOn(extensions::ExtensionRegistryFactory::GetInstance());
  DependsOn(extensions::EventRouterFactory::GetInstance());
  DependsOn(
      extensions::ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
}

OneDrivePrefObserverFactory::~OneDrivePrefObserverFactory() = default;

std::unique_ptr<KeyedService>
OneDrivePrefObserverFactory::BuildServiceInstanceForBrowserContext(
    content::BrowserContext* context) const {
  if (!features::IsUploadOfficeToCloudEnabled() ||
      !features::IsMicrosoftOneDriveIntegrationForEnterpriseEnabled()) {
    return nullptr;
  }

  Profile* profile = Profile::FromBrowserContext(context);
  return OneDrivePrefObserver::Create(profile);
}

bool OneDrivePrefObserverFactory::ServiceIsCreatedWithBrowserContext() const {
  return true;
}

void OneDrivePrefObserver::BroadcastModeChanged(Mount mode) {
  if (!event_router_) {
    CHECK_IS_TEST();
    return;
  }

  MountInfo metadata;
  metadata.mode = mode;

  auto event = std::make_unique<extensions::Event>(
      extensions::events::ODFS_CONFIG_PRIVATE_MOUNT_CHANGED,
      extensions::api::odfs_config_private::OnMountChanged::kEventName,
      extensions::api::odfs_config_private::OnMountChanged::Create(
          std::move(metadata)),
      profile_);

  event_router_->BroadcastEvent(std::move(event));
}

void OneDrivePrefObserver::BroadcastAccountRestrictionsChanged(
    base::ListValue restrictions) {
  if (!event_router_) {
    CHECK_IS_TEST();
    return;
  }

  std::vector<std::string> restrictions_vector;
  for (auto& restriction : restrictions) {
    if (restriction.is_string()) {
      restrictions_vector.emplace_back(restriction.GetString());
    }
  }

  AccountRestrictionsInfo event_data;
  event_data.restrictions = std::move(restrictions_vector);

  auto event = std::make_unique<extensions::Event>(
      extensions::events::ODFS_CONFIG_PRIVATE_ACCOUNT_RESTRICTIONS_CHANGED,
      extensions::api::odfs_config_private::OnAccountRestrictionsChanged::
          kEventName,
      extensions::api::odfs_config_private::OnAccountRestrictionsChanged::
          Create(event_data),
      profile_);

  event_router_->BroadcastEvent(std::move(event));
}

void OneDrivePrefObserver::OnAppUpdate(const apps::AppUpdate& update) {
  // Set the default for the M365 PWA to open supported links inside the PWA for
  // the automated Clippy flow. This will only be done once when the M365 PWA is
  // ready (either on install or after user session start) to allow the user to
  // modify this behavior.
  if (update.AppId() == ash::kMicrosoft365AppId && update.ReadinessChanged() &&
      update.Readiness() == apps::Readiness::kReady) {
    MaybeSetSupportedLinksPreference();
  }
}

void OneDrivePrefObserver::MaybeSetSupportedLinksPreference() {
  if (!cloud_upload::IsMicrosoftOfficeOneDriveIntegrationAutomated(profile_)) {
    return;
  }

  PrefService* pref_service = profile_->GetPrefs();
  if (pref_service->GetBoolean(ash::prefs::kM365SupportedLinkDefaultSet)) {
    return;
  }

  pref_service->SetBoolean(ash::prefs::kM365SupportedLinkDefaultSet, true);
  apps::AppServiceProxyFactory::GetForProfile(profile_)
      ->SetSupportedLinksPreference(ash::kMicrosoft365AppId);
}

void OneDrivePrefObserver::OnAppRegistryCacheWillBeDestroyed(
    apps::AppRegistryCache* cache) {
  app_registry_cache_observer_.Reset();
}

}  // namespace chromeos::cloud_storage
