// 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 "components/unified_consent/url_keyed_data_collection_consent_helper.h"

#include <algorithm>
#include <map>
#include <set>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/sync/base/data_type.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_service_observer.h"
#include "components/sync/service/sync_service_utils.h"
#include "components/unified_consent/pref_names.h"

namespace unified_consent {

namespace {

class PrefBasedUrlKeyedDataCollectionConsentHelper
    : public UrlKeyedDataCollectionConsentHelper {
 public:
  explicit PrefBasedUrlKeyedDataCollectionConsentHelper(
      PrefService* pref_service);

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

  ~PrefBasedUrlKeyedDataCollectionConsentHelper() override = default;

  // UrlKeyedDataCollectionConsentHelper:
  State GetConsentState() override;

 private:
  void OnPrefChanged();
  raw_ptr<PrefService> pref_service_;  // weak (must outlive this)
  PrefChangeRegistrar pref_change_registrar_;
};

class SyncBasedUrlKeyedDataCollectionConsentHelper
    : public UrlKeyedDataCollectionConsentHelper,
      syncer::SyncServiceObserver {
 public:
  SyncBasedUrlKeyedDataCollectionConsentHelper(
      syncer::SyncService* sync_service,
      std::set<syncer::DataType> sync_data_types,
      bool require_sync_feature_enabled);

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

  ~SyncBasedUrlKeyedDataCollectionConsentHelper() override;

  // UrlKeyedDataCollectionConsentHelper:
  State GetConsentState() override;

  // syncer::SyncServiceObserver:
  void OnStateChanged(syncer::SyncService* sync) override;
  void OnSyncShutdown(syncer::SyncService* sync) override;

 private:
  void UpdateSyncDataTypeStates();

  const bool require_sync_feature_enabled_;
  raw_ptr<syncer::SyncService> sync_service_;
  std::map<syncer::DataType, syncer::UploadState> sync_data_type_states_;
  bool sync_feature_state_ = false;
};

PrefBasedUrlKeyedDataCollectionConsentHelper::
    PrefBasedUrlKeyedDataCollectionConsentHelper(PrefService* pref_service)
    : pref_service_(pref_service) {
  pref_change_registrar_.Init(pref_service_);
  pref_change_registrar_.Add(
      prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
      base::BindRepeating(
          &PrefBasedUrlKeyedDataCollectionConsentHelper::OnPrefChanged,
          base::Unretained(this)));
}

UrlKeyedDataCollectionConsentHelper::State
PrefBasedUrlKeyedDataCollectionConsentHelper::GetConsentState() {
  // There's no initializing state for pref-based helpers.
  return pref_service_->GetBoolean(
             prefs::kUrlKeyedAnonymizedDataCollectionEnabled)
             ? State::kEnabled
             : State::kDisabled;
}

void PrefBasedUrlKeyedDataCollectionConsentHelper::OnPrefChanged() {
  FireOnStateChanged();
}

SyncBasedUrlKeyedDataCollectionConsentHelper::
    SyncBasedUrlKeyedDataCollectionConsentHelper(
        syncer::SyncService* sync_service,
        std::set<syncer::DataType> sync_data_types,
        bool require_sync_feature_enabled)
    : require_sync_feature_enabled_(require_sync_feature_enabled),
      sync_service_(sync_service) {
  DCHECK(!sync_data_types.empty());

  for (const auto& sync_data_type : sync_data_types) {
    sync_data_type_states_[sync_data_type] = syncer::UploadState::NOT_ACTIVE;
  }
  UpdateSyncDataTypeStates();

  if (sync_service_)
    sync_service_->AddObserver(this);
}

SyncBasedUrlKeyedDataCollectionConsentHelper::
    ~SyncBasedUrlKeyedDataCollectionConsentHelper() {
  if (sync_service_)
    sync_service_->RemoveObserver(this);
}

// Note: This method must only consume cached state (not query anything from
// SyncService), to ensure that the state-change detection in OnStateChanged()
// works correctly.
UrlKeyedDataCollectionConsentHelper::State
SyncBasedUrlKeyedDataCollectionConsentHelper::GetConsentState() {
  if (require_sync_feature_enabled_ && !sync_feature_state_) {
    return State::kDisabled;
  }

  // Any sync type that's NOT_ACTIVE makes the whole consent kDisabled.
  for (const auto& sync_data_type_states : sync_data_type_states_) {
    if (sync_data_type_states.second == syncer::UploadState::NOT_ACTIVE) {
      return State::kDisabled;
    }
  }

  // If no sync type is NOT_ACTIVE, any sync type still INITIALIZING makes the
  // whole consent kInitializing.
  for (const auto& sync_data_type_states : sync_data_type_states_) {
    if (sync_data_type_states.second == syncer::UploadState::INITIALIZING) {
      return State::kInitializing;
    }
  }

  DCHECK(std::ranges::all_of(sync_data_type_states_, [](auto& state) {
    return state.second == syncer::UploadState::ACTIVE;
  })) << "Nothing is NOT_ACTIVE or INITIALIZING, so all must be ACTIVE.";
  return State::kEnabled;
}

void SyncBasedUrlKeyedDataCollectionConsentHelper::OnStateChanged(
    syncer::SyncService* sync_service) {
  DCHECK_EQ(sync_service_, sync_service);
  auto old_state = GetConsentState();
  UpdateSyncDataTypeStates();
  if (old_state != GetConsentState()) {
    FireOnStateChanged();
  }
}

void SyncBasedUrlKeyedDataCollectionConsentHelper::OnSyncShutdown(
    syncer::SyncService* sync_service) {
  DCHECK_EQ(sync_service_, sync_service);
  sync_service_->RemoveObserver(this);
  sync_service_ = nullptr;
}

void SyncBasedUrlKeyedDataCollectionConsentHelper::UpdateSyncDataTypeStates() {
  // TODO(crbug.com/40067025): Remove the `require_sync_feature_enabled` param
  // once kReplaceSyncPromosWithSignInPromos is launched.
  if (require_sync_feature_enabled_) {
    sync_feature_state_ =
        sync_service_ && sync_service_->IsSyncFeatureEnabled();
  }
  for (auto& [data_type, upload_state] : sync_data_type_states_) {
    upload_state = syncer::GetUploadToGoogleState(sync_service_, data_type);
  }
}

}  // namespace

UrlKeyedDataCollectionConsentHelper::UrlKeyedDataCollectionConsentHelper() =
    default;
UrlKeyedDataCollectionConsentHelper::~UrlKeyedDataCollectionConsentHelper() =
    default;

// static
std::unique_ptr<UrlKeyedDataCollectionConsentHelper>
UrlKeyedDataCollectionConsentHelper::NewAnonymizedDataCollectionConsentHelper(
    PrefService* pref_service) {
  return std::make_unique<PrefBasedUrlKeyedDataCollectionConsentHelper>(
      pref_service);
}

// static
std::unique_ptr<UrlKeyedDataCollectionConsentHelper>
UrlKeyedDataCollectionConsentHelper::NewPersonalizedDataCollectionConsentHelper(
    syncer::SyncService* sync_service) {
  return std::make_unique<SyncBasedUrlKeyedDataCollectionConsentHelper>(
      sync_service,
      std::set<syncer::DataType>({syncer::DataType::HISTORY_DELETE_DIRECTIVES}),
      /*require_sync_feature_enabled=*/false);
}

// static
std::unique_ptr<UrlKeyedDataCollectionConsentHelper>
UrlKeyedDataCollectionConsentHelper::
    NewPersonalizedBookmarksDataCollectionConsentHelper(
        syncer::SyncService* sync_service,
        bool require_sync_feature_enabled) {
  return std::make_unique<SyncBasedUrlKeyedDataCollectionConsentHelper>(
      sync_service, std::set<syncer::DataType>({syncer::DataType::BOOKMARKS}),
      require_sync_feature_enabled);
}

bool UrlKeyedDataCollectionConsentHelper::IsEnabled() {
  return GetConsentState() == State::kEnabled;
}

void UrlKeyedDataCollectionConsentHelper::AddObserver(Observer* observer) {
  observer_list_.AddObserver(observer);
}
void UrlKeyedDataCollectionConsentHelper::RemoveObserver(Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

void UrlKeyedDataCollectionConsentHelper::FireOnStateChanged() {
  for (auto& observer : observer_list_)
    observer.OnUrlKeyedDataCollectionConsentStateChanged(this);
}

}  // namespace unified_consent
