// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_POLICY_CLOUD_POLICY_INVALIDATOR_H_
#define CHROME_BROWSER_POLICY_CLOUD_POLICY_INVALIDATOR_H_

#include <stdint.h>

#include <memory>
#include <optional>
#include <string>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "components/invalidation/invalidation_listener.h"
#include "components/policy/core/common/cloud/cloud_policy_core.h"
#include "components/policy/core/common/cloud/cloud_policy_store.h"
#include "components/policy/core/common/cloud/policy_invalidation_scope.h"

namespace base {
class Clock;
class SequencedTaskRunner;
}  // namespace base

namespace policy {

// Listens for and provides policy invalidations.
// Listens to incoming invalidations only when `CloudPolicyCore` is connected
// and `CloudPolicyRefreshScheduler` is available, because otherwise there's
// no possibility to refresh policies.
class PolicyInvalidator : public invalidation::InvalidationListener::Observer,
                          public CloudPolicyCore::Observer,
                          public CloudPolicyStore::Observer {
 public:
  // The value for minimal delay of triggering a policy refresh after an
  // received invalidation.
  static constexpr base::TimeDelta kMinFetchDelay = base::Milliseconds(20);

  // The default, min and max values for `max_fetch_delay_`.
  static constexpr base::TimeDelta kMaxFetchDelayDefault =
      base::Milliseconds(10000);
  static constexpr base::TimeDelta kMaxFetchDelayMin = base::Milliseconds(1000);
  static constexpr base::TimeDelta kMaxFetchDelayMax =
      base::Milliseconds(300000);

  // The grace period, to allow for invalidations to be received
  // once the invalidation service starts up.
  static constexpr base::TimeDelta kInvalidationGracePeriod = base::Seconds(10);

  PolicyInvalidator(const PolicyInvalidator&) = delete;
  PolicyInvalidator& operator=(const PolicyInvalidator&) = delete;
  ~PolicyInvalidator() override;

  // The highest invalidation version that was handled already.
  int64_t highest_handled_invalidation_version() const;

  // CloudPolicyCore::Observer:
  void OnCoreConnected(CloudPolicyCore* core) override;
  void OnRefreshSchedulerStarted(CloudPolicyCore* core) override;
  void OnCoreDisconnecting(CloudPolicyCore* core) override;

  // CloudPolicyStore::Observer:
  void OnStoreLoaded(CloudPolicyStore* store) override;
  void OnStoreError(CloudPolicyStore* store) override;

  // InvalidationListener::Observer:
  void OnExpectationChanged(
      invalidation::InvalidationsExpected expected) override;
  void OnInvalidationReceived(
      const invalidation::DirectInvalidation& invalidation) override;

  std::string GetType() const override;

 protected:
  class PolicyInvalidationHandler;

  // |scope| indicates the invalidation scope that this invalidator
  // is responsible for.
  // |invalidation_listener| provides invalidations and is observed during the
  // whole invalidator's lifetime. Must remain valid until the invalidator is
  // destroyed.
  // |core| is the cloud policy core which connects the various policy objects.
  // It must remain valid until Shutdown is called.
  // |clock| is used to get the current time.
  // |device_local_account_id| is a unique identity for invalidator with
  // DeviceLocalAccount |scope| to have unique owner name. May be let empty
  // if scope is not DeviceLocalAccount.
  PolicyInvalidator(
      std::string type,
      PolicyInvalidationScope scope,
      invalidation::InvalidationListener* invalidation_listener,
      CloudPolicyCore* core,
      const base::Clock* clock,
      const std::string& device_local_account_id,
      std::unique_ptr<PolicyInvalidationHandler> policy_invalidation_handler);

  // Handles policy refresh depending on invalidations availability and incoming
  // invalidations.
  class PolicyInvalidationHandler {
   public:
    PolicyInvalidationHandler(
        PolicyInvalidationScope scope,
        CloudPolicyCore* core,
        const base::Clock* clock,
        scoped_refptr<base::SequencedTaskRunner> task_runner,
        const char* policy_refresh_metric_name,
        const char* policy_invalidation_metric_name);

    virtual ~PolicyInvalidationHandler();

    // Handles an invalidation to the policy.
    void HandleInvalidation(
        const invalidation::DirectInvalidation& invalidation);

    // Informs the core's refresh scheduler about whether invalidations are
    // enabled.
    void UpdateInvalidationsEnabled(bool invalidations_enabled);

    // Update |max_fetch_delay_| based on the given policy map.
    void UpdateMaxFetchDelay(const PolicyMap& policy_map);

    void HandlePolicyRefresh(CloudPolicyStore* store);

    void FinishInvalidationHandling();

    int64_t highest_handled_invalidation_version() const {
      return highest_handled_invalidation_version_;
    }

    bool IsCoreReady() const {
      return core_->IsConnected() && core_->refresh_scheduler() &&
             core_->store();
    }

    CloudPolicyCore* core() const { return core_; }

   private:
    void set_max_fetch_delay(base::TimeDelta delay);

    // Performs the policy fetch.
    void RefreshPolicy();

    // Returns true if invalidations have been enabled for a while.
    // This is a heuristic attempt to avoid counting initial policy fetches as
    // invalidation-triggered.
    // See https://codereview.chromium.org/213743014 for more details.
    bool AreInvalidationsEnabledForAWhile() const;

    // The cloud policy core.
    const raw_ptr<CloudPolicyCore> core_;

    // The timestamp at which invalidations became enabled. If unset then
    // invalidations are considered disabled.
    std::optional<base::Time> invalidations_enabled_time_;

    // If set, contains the currently handled invalidation, i.e. a
    // policy refresh has been triggered due to it, but the policy refresh has
    // not finished yet.
    std::optional<invalidation::DirectInvalidation> in_progress_invalidation_;

    // The highest invalidation version that was handled already.
    int64_t highest_handled_invalidation_version_ = 0;

    // The hash value of the current policy. This is used to determine if a new
    // policy is different from the current one.
    std::optional<size_t> policy_hash_value_;

    // The maximum random delay, between receiving an invalidation and
    // fetching the new policy.
    base::TimeDelta max_fetch_delay_ = kMaxFetchDelayDefault;

    // The clock.
    const raw_ptr<const base::Clock> clock_;

    // Schedules delayed tasks.
    const scoped_refptr<base::SequencedTaskRunner> task_runner_;

    // Metric names to record.
    const char* const policy_refresh_metric_name_;
    const char* const policy_invalidation_metric_name_;

    // A thread checker to make sure that callbacks are invoked on the correct
    // thread.
    THREAD_CHECKER(thread_checker_);

    // WeakPtrFactory used to create callbacks to this object.
    base::WeakPtrFactory<PolicyInvalidationHandler> weak_factory_{this};
  };

  // Returns true if ready to receive invalidations.
  bool IsRegistered() const;

  // Returns true if ready to receive invalidations and invalidations are
  // enabled.
  bool AreInvalidationsEnabled() const;

  const std::string type_;

  std::unique_ptr<PolicyInvalidationHandler> policy_invalidation_handler_;

  // The invalidation scope this invalidator is responsible for.
  const PolicyInvalidationScope scope_;

  // The cloud policy core.
  raw_ptr<CloudPolicyCore> core_;

  base::ScopedObservation<CloudPolicyCore, PolicyInvalidator> core_observation_{
      this};
  base::ScopedObservation<CloudPolicyStore, PolicyInvalidator>
      store_observation_{this};

  raw_ptr<invalidation::InvalidationListener> invalidation_listener_;

  invalidation::InvalidationsExpected are_invalidations_expected_ =
      invalidation::InvalidationsExpected::kMaybe;

  base::ScopedObservation<invalidation::InvalidationListener, PolicyInvalidator>
      invalidation_listener_observation_{this};

  const std::string device_local_account_id_;

  // A thread checker to make sure that callbacks are invoked on the correct
  // thread.
  THREAD_CHECKER(thread_checker_);
};

}  // namespace policy

#endif  // CHROME_BROWSER_POLICY_CLOUD_POLICY_INVALIDATOR_H_
