// 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.

#ifndef COMPONENTS_ORIGIN_TRIALS_BROWSER_ORIGIN_TRIALS_H_
#define COMPONENTS_ORIGIN_TRIALS_BROWSER_ORIGIN_TRIALS_H_

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

#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/origin_trials/common/origin_trials_persistence_provider.h"
#include "content/public/browser/origin_trial_status_change_details.h"
#include "content/public/browser/origin_trials_controller_delegate.h"
#include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
#include "third_party/blink/public/mojom/origin_trials/origin_trial_feature.mojom-shared.h"

using content::OriginTrialStatusChangeDetails;

namespace url {
class Origin;
}

namespace origin_trials {

// Implementation of persistent origin trials for the browser process.
//
// This class manages persistent origin trials, allowing the browser to check
// if a given trial is enabled or not.
//
// Persisting the enabled trials is handled by the `persistence_provider` passed
// in through the constructor.
class OriginTrials : public KeyedService,
                     public content::OriginTrialsControllerDelegate {
 public:
  using ObserverMap = std::map<
      std::string,
      base::ObserverList<content::OriginTrialsControllerDelegate::Observer>>;

  OriginTrials(
      std::unique_ptr<OriginTrialsPersistenceProvider> persistence_provider,
      std::unique_ptr<blink::TrialTokenValidator> token_validator);

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

  ~OriginTrials() override;

  // content::OriginTrialsControllerDelegate
  void AddObserver(Observer* observer) override;
  void RemoveObserver(Observer* observer) override;
  void PersistTrialsFromTokens(
      const url::Origin& origin,
      const url::Origin& partition_origin,
      const base::span<const std::string> header_tokens,
      const base::Time current_time,
      std::optional<ukm::SourceId> source_id) override;
  void PersistAdditionalTrialsFromTokens(
      const url::Origin& origin,
      const url::Origin& partition_origin,
      base::span<const url::Origin> script_origins,
      const base::span<const std::string> header_tokens,
      const base::Time current_time,
      std::optional<ukm::SourceId> source_id) override;
  bool IsFeaturePersistedForOrigin(const url::Origin& origin,
                                   const url::Origin& partition_origin,
                                   blink::mojom::OriginTrialFeature feature,
                                   const base::Time current_time) override;
  base::flat_set<std::string> GetPersistedTrialsForOrigin(
      const url::Origin& origin,
      const url::Origin& partition_origin,
      base::Time current_time) override;
  void ClearPersistedTokens() override;

 private:
  friend class OriginTrialsTest;
  std::unique_ptr<OriginTrialsPersistenceProvider> persistence_provider_;
  std::unique_ptr<blink::TrialTokenValidator> trial_token_validator_;
  ObserverMap observer_map_;

  void NotifyStatusChange(const std::string& trial,
                          const OriginTrialStatusChangeDetails& details);
  void NotifyPersistedTokensCleared();

  // Returns true if `origin` can use a token made for `token_origin`. For
  // `origin` to use `token_origin`'s token, at least one of the following must
  // be true:
  //  - `origin` equals `token_origin`
  //  - `match_subdomains` is true and `origin` is a subdomain of `token_origin`
  // NOTE: This is meant to mirror the logic used in
  // `blink::TrialToken::ValidateOrigin()`.
  // TODO(crbug.com/40189223): Find a way to share/reuse the logic in
  // `blink::TrialToken`. Otherwise, the logic could change in one place and not
  // the other.
  bool MatchesTokenOrigin(const url::Origin& token_origin,
                          bool match_subdomains,
                          const url::Origin& origin) const;

  void PersistTokensInternal(const url::Origin& origin,
                             const url::Origin& partition_origin,
                             base::span<const url::Origin> script_origins,
                             const base::span<const std::string> header_tokens,
                             const base::Time current_time,
                             std::optional<ukm::SourceId> source_id,
                             bool append_only);

  // Helper to return the still-valid persisted trials, with an optional
  // `trial_feature_match` which can be passed to ensure we only validate
  // and return the trial if it enables the desired trial feature.
  // If no `trial_feature_match` is provided, it will return all persisted
  // trials that are still valid.
  base::flat_set<std::string> GetPersistedTrialsForOriginWithMatch(
      const url::Origin& origin,
      const url::Origin& partition_origin,
      const base::Time current_time,
      const std::optional<blink::mojom::OriginTrialFeature> trial_feature_match)
      const;

  // Update the stored tokens for `token_origin` with the `new_tokens`,
  // partitioned by `partition_site`.
  // If `append_only` is set to true, existing tokens for `token_origin` that
  // aren't found in `new_tokens` will be cleared, unless they match subdomains,
  // in which case they will only be cleared if `document_origin` equals
  // `token_origin`.
  void UpdatePersistedTokenSet(const url::Origin& document_origin,
                               const url::Origin& token_origin,
                               base::span<const blink::TrialToken> new_tokens,
                               const std::string& partition_site,
                               std::optional<ukm::SourceId> source_id,
                               bool append_only);

  // Get the 'site' used as the partitioning key for trial tokens.
  //
  // The key is the eTLD+1 of the `origin`, taking private registries such as
  // blogspot.com into account.
  static std::string GetTokenPartitionSite(const url::Origin& origin);
};

}  // namespace origin_trials

#endif  // COMPONENTS_ORIGIN_TRIALS_BROWSER_ORIGIN_TRIALS_H_
