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

#ifndef EXTENSIONS_BROWSER_CWS_INFO_SERVICE_H_
#define EXTENSIONS_BROWSER_CWS_INFO_SERVICE_H_

#include <optional>
#include <string>

#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/timer/timer.h"
#include "components/keyed_service/core/keyed_service.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_id.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

class PrefService;

namespace content {
class BrowserContext;
}  // namespace content

namespace network {
class SharedURLLoaderFactory;
class SimpleURLLoader;
}  // namespace network

namespace extensions {

class Extension;
class ExtensionPrefs;
class ExtensionRegistry;
class BatchGetStoreMetadatasResponse;

BASE_DECLARE_FEATURE(kCWSInfoFastCheck);

// This is an interface class to allow for easy mocking.
class CWSInfoServiceInterface {
 public:
  virtual ~CWSInfoServiceInterface() = default;

  // Synchronously checks if the extension is currently live in CWS.
  // If the information is not available immediately (i.e., not stored in local
  // cache), does not return a value.
  virtual std::optional<bool> IsLiveInCWS(const Extension& extension) const = 0;

  enum class CWSViolationType {
    kNone = 0,
    kMalware = 1,
    kPolicy = 2,
    kMinorPolicy = 3,
    // New enum values must go above here
    kUnknown
  };
  struct CWSInfo {
    // This extension is present in CWS.
    bool is_present = false;
    // This extension is currently published and downloadable from CWS.
    bool is_live = false;
    // The last time the extension was updated in CWS. Only valid if `is_live`
    // is true.
    base::Time last_update_time;
    // The following fields are only valid if `is_present` is true.
    // If the extension has been taken down, i.e., no longer live, this
    // represents the violation type that caused the take-down.
    CWSViolationType violation_type = CWSViolationType::kNone;
    // The extension was unpublished from CWS by the developer a while ago.
    bool unpublished_long_ago = false;
    // The extension does not display proper privacy practice information in
    // CWS.
    bool no_privacy_practice = false;
  };
  virtual std::optional<CWSInfo> GetCWSInfo(
      const Extension& extension) const = 0;

  // Initiates a fetch from CWS if:
  // - at least one installed extension is missing CWS metadata information
  // - Enough time (default: 24 hours) has elapsed since the last time the
  //   metadata was fetched.
  virtual void CheckAndMaybeFetchInfo() = 0;

  class Observer : public base::CheckedObserver {
   public:
    // This callback is invoked when there is a change in store metadata
    // saved by the service.
    virtual void OnCWSInfoChanged() {}
  };
  // Use these methods to (de)register for changes in the CWS metadata retrieved
  // by the service.
  virtual void AddObserver(Observer* observer) = 0;
  virtual void RemoveObserver(Observer* observer) = 0;
};

// This service retrieves information about installed extensions from CWS
// periodically (default: every 24 hours). It is used exclusively on the
// browser UI thread. The service also supports out-of-cycle fetch requests for
// use cases where waiting for up to 24 hours for fresh state is not desirable
// (for example, when the ExtensionsUnpublishedAvailability policy setting
// changes). Only extensions that update from CWS are queried.
class CWSInfoService : public CWSInfoServiceInterface, public KeyedService {
 public:
  explicit CWSInfoService(content::BrowserContext* browser_context);

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

  // CWSInfoServiceInterface:
  std::optional<bool> IsLiveInCWS(const Extension& extension) const override;
  std::optional<CWSInfo> GetCWSInfo(const Extension& extension) const override;
  void CheckAndMaybeFetchInfo() override;
  void AddObserver(Observer* observer) override;
  void RemoveObserver(Observer* observer) override;

  // KeyedService:
  void Shutdown() override;

  // Helpers
  static CWSInfoService::CWSViolationType GetViolationTypeFromString(
      const std::string& violation_type_str);

  // Testing helpers
  std::string GetRequestURLForTesting() const;
  int GetStartupDelayForTesting() const;
  int GetCheckIntervalForTesting() const;
  int GetFetchIntervalForTesting() const;
  base::Time GetCWSInfoTimestampForTesting() const;
  base::Time GetCWSInfoFetchErrorTimestampForTesting() const;
  void SetMaxExtensionIdsPerRequestForTesting(int max);
  static void SetSkipApiCheckForTesting(bool skip_api_key_check);
  void SetSharedURLLoaderFactoryForTesting(
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);

 protected:
  // Only used for testing to create a fake derived class.
  CWSInfoService();

  // This method schedules an info check after specified `seconds`.
  void ScheduleCheck(int seconds);

  // This method prepares request protos to fetch CWS metadata. A CWS fetch
  // operation can consist of multiple request protos when the number of
  // installed extensions exceeds the max ids supported per request (100). The
  // request protos, extension ids and other data associated with the fetch are
  // returned in a `FetchContext`. The method also outputs a
  // `new_info_requested` that indicates if at least one of the installed
  // extensions is missing CWS metadata information.
  struct FetchContext;
  std::unique_ptr<FetchContext> CreateRequests(
      bool& /*output=*/new_info_requested);

  // Sends a single network request associated with a CWS info fetch.
  void SendRequest();

  // Handles the server response associated with a single network request.
  void OnResponseReceived(std::optional<std::string> response);

  // Saves data to prefs if the response data is different from the saved data.
  // Returns true if the response data is saved, false otherwise.
  bool MaybeSaveResponseToPrefs(
      const BatchGetStoreMetadatasResponse& response_proto);

  const raw_ptr<content::BrowserContext> browser_context_ = nullptr;
  const raw_ptr<PrefService> pref_service_ = nullptr;
  const raw_ptr<ExtensionPrefs> extension_prefs_ = nullptr;
  const raw_ptr<ExtensionRegistry> extension_registry_ = nullptr;

  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
  std::unique_ptr<network::SimpleURLLoader> url_loader_;

  // Stores context about a fetch operation in progress. The service only
  // supports one fetch operation at a time.
  std::unique_ptr<FetchContext> active_fetch_;
  // Each request associated with a fetch can have a maximum of 100 extension
  // ids. This parameter can be changed for testing.
  int max_ids_per_request_;

  // Stats for requests, responses and errors.
  uint32_t info_requests_ = 0;
  uint32_t info_responses_ = 0;
  uint32_t info_errors_ = 0;
  // Counts the number of times the downloaded metadata was different from that
  // currently saved.
  uint32_t info_changes_ = 0;
  // A timer used to periodically check if CWS information needs to be fetched.
  base::OneShotTimer info_check_timer_;
  // Time from startup to first check of CWS information.
  int startup_delay_secs_ = 0;
  // Time interval between fetches from CWS info server. The interval value
  // varies +/-25% from default of 24 hours for every fetch.
  int current_fetch_interval_secs_ = 0;

  // List of observers that are notified whenever new CWS information is saved.
  base::ObserverList<Observer> observers_;

  friend class CWSInfoServiceTest;

  base::WeakPtrFactory<CWSInfoService> weak_factory_{this};
};

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_CWS_INFO_SERVICE_H_
