// Copyright 2021 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_UI_HATS_TRUST_SAFETY_SENTIMENT_SERVICE_H_
#define CHROME_BROWSER_UI_HATS_TRUST_SAFETY_SENTIMENT_SERVICE_H_

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_multi_source_observation.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "chrome/browser/download/download_item_warning_data.h"
#include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_observer.h"
#include "components/browsing_data/core/browsing_data_utils.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/core/browser/password_protection/metrics_util.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"

using PasswordProtectionUIType = safe_browsing::WarningUIType;
using PasswordProtectionUIAction = safe_browsing::WarningAction;

inline constexpr base::TimeDelta kPasswordChangeInactivity = base::Minutes(30);
inline constexpr base::TimeDelta kSafetyHubSurveyDelay = base::Minutes(10);

// Service which receives events from Trust & Safety features and determines
// whether or not to launch a HaTS survey on the NTP for the user.
class TrustSafetySentimentService
    : public KeyedService,
      public ProfileObserver,
      public metrics::DesktopSessionDurationTracker::Observer {
 public:
  explicit TrustSafetySentimentService(Profile* profile);
  ~TrustSafetySentimentService() override;

  // Called when the user opens an NTP. This allows the service to update its
  // eligibility logic, and potentially show a survey. Virtual to allow mocking
  // in tests.
  virtual void OpenedNewTabPage();

  // Called when the user interacts with the privacy settings on
  // chrome://settings in |web_contents|. Interaction in this context could be
  // using a link row on the privacy settings card. Calling this allows the
  // service to monitor |web_contents| to determine if the user stays on
  // settings for the required time. Virtual to allow mocking in tests.
  virtual void InteractedWithPrivacySettings(
      content::WebContents* web_contents);

  // Called when the user runs safety check. This is immediately considered as a
  // trigger action. Virtual to allow mocking in tests.
  virtual void RanSafetyCheck();

  // Called when the user opens Page Info.
  virtual void PageInfoOpened();

  // Called when the user interacts in some way with Page Info.
  virtual void InteractedWithPageInfo();

  // Called when the user saves a password via the manage passwords UI. This is
  // the native UI shown when Chrome detects a password has been entered into
  // the web page.
  virtual void SavedPassword();

  // Called when the user closes Page Info. If Page Info was opened for the
  // target time, or the user interacted with it while it was open, a trigger
  // action is recorded.
  virtual void PageInfoClosed();

  // Called when the user visits chrome://settings/passwords. Calling this
  // allows the service to monitor |web_contents| to determine if the user
  // remains on settings after visiting the page for the required time. Virtual
  // to allow mocking in tests.
  virtual void OpenedPasswordManager(content::WebContents* web_contents);

  // Called when the user saves a card through the native UI bubble shown after
  // the user uses a card on a website.
  virtual void SavedCard();

  // Called when the user runs password check. Virtual to allow mocking in
  // tests.
  virtual void RanPasswordCheck();

  // Called when the user deletes data from Clear Browsing Data dialog.
  virtual void ClearedBrowsingData(browsing_data::BrowsingDataType datatype);

  // Called when the user finishes the privacy guide. Virtual to allow mocking
  // in tests.
  virtual void FinishedPrivacyGuide();

  // Profile Observer:
  void OnOffTheRecordProfileCreated(Profile* off_the_record) override;
  void OnProfileWillBeDestroyed(Profile* profile) override;

  // metrics::DesktopSessionDurationTracker::Observer
  void OnSessionEnded(base::TimeDelta session_length,
                      base::TimeTicks session_end) override;

  // The feature areas that the service delivers HaTS surveys for. Each feature
  // area is associated with a different Listnr survey, and has a different set
  // of Product Specific Data (PSD). kIneligible is an exception, and
  // is not associated with any survey, but rather represents the collection of
  // features for which interaction with should also be considered when
  // determining elibigility for a survey.

  // These values are persisted to logs and entries should not be renumbered or
  // reused and kept up to date with TrustSafetySentimentFeatureArea in
  // enums.xml.
  enum class FeatureArea {
    kIneligible = 0,
    kPrivacySettings = 1,
    kTrustedSurface = 2,
    kTransactions = 3,
    // kPrivacySandbox3ConsentAccept = 4, // DEPRECATED.
    // kPrivacySandbox3ConsentDecline = 5, // DEPRECATED.
    // kPrivacySandbox3NoticeDismiss = 6, // DEPRECATED.
    // kPrivacySandbox3NoticeOk = 7, // DEPRECATED.
    // kPrivacySandbox3NoticeSettings = 8, // DEPRECATED.
    // kPrivacySandbox3NoticeLearnMore = 9, // DEPRECATED.
    kSafetyCheck = 10,
    kPasswordCheck = 11,
    kBrowsingData = 12,
    kPrivacyGuide = 13,
    kControlGroup = 14,
    // kPrivacySandbox4ConsentAccept = 15, // DEPRECATED.
    // kPrivacySandbox4ConsentDecline = 16, // DEPRECATED.
    // kPrivacySandbox4NoticeOk = 17, // DEPRECATED.
    // kPrivacySandbox4NoticeSettings = 18, // DEPRECATED.
    kSafeBrowsingInterstitial = 19,
    kDownloadWarningUI = 20,
    kPasswordProtectionUI = 21,
    kSafetyHubNotification = 22,
    kSafetyHubInteracted = 23,
    kMaxValue = kSafetyHubInteracted,
  };

  // Called when the user interacts with a safe browsing blocking page.
  virtual void InteractedWithSafeBrowsingInterstitial(
      bool did_proceed,
      safe_browsing::SBThreatType threat_type);

  // Called when the user completes terminal action within a download warning.
  // These actions can include: DISCARD, and PROCEED.
  virtual void InteractedWithDownloadWarningUI(
      DownloadItemWarningData::WarningSurface surface,
      DownloadItemWarningData::WarningAction action);

  // Called when user clicks to protect/reset/check their password on a password
  // protection UI. This triggers a survey if the user has not finished changing
  // their password after a certain period of time.
  virtual void ProtectResetOrCheckPasswordClicked(
      PasswordProtectionUIType ui_type);

  // Called when a user sees a password protection warning and decides to ignore
  // the warning, close the warning, or mark the warning as legitimate.
  virtual void PhishedPasswordUpdateNotClicked(
      PasswordProtectionUIType ui_type,
      PasswordProtectionUIAction action);

  // Called when a user finishes updating their phished password after seeing a
  // warning.
  virtual void PhishedPasswordUpdateFinished();

  // Checks that this feature area is valid for the current version.
  static bool VersionCheck(FeatureArea feature_area);

  // Gets the HaTS trigger for a feature area.
  static std::string GetHatsTriggerForFeatureArea(FeatureArea feature_area);

  // Performs a FeatureArea and Version-specific dice roll.
  // Returns true if succeeds, else false.
  static bool ProbabilityCheck(FeatureArea feature_area);

  // Triggers a survey for Safety Hub for the given feature area (visiting SH or
  // seeing a notification).
  virtual void TriggerSafetyHubSurvey(
      TrustSafetySentimentService::FeatureArea feature_area,
      std::map<std::string, bool> product_specific_data);

 private:
  friend class TrustSafetySentimentServiceTest;
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
                           Eligibility_NtpOpens);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, Eligibility_Time);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, TriggerProbability);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
                           TriggersClearOnLaunch);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
                           SettingsWatcher_PrivacySettings);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
                           SettingsWatcher_PasswordManager);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, RanSafetyCheck);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
                           PrivacySettingsProductSpecificData);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
                           Eligibility_V1FeatureWhileV2Enabled);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_SafetyCheck);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_TrustedSurface);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_PasswordCheck);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_BrowsingData);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
                           V2_BrowsingData_NotInterested);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_PrivacyGuide);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_ControlGroup);
  FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
                           SafetyHubInteractionState);

  // Struct representing a trigger (user action relevant to T&S) that previously
  // occurred, and is awaiting the appropriate eligibility steps before causing
  // a survey to be shown.
  struct PendingTrigger {
    PendingTrigger();
    PendingTrigger(const PendingTrigger& other);
    PendingTrigger(const std::map<std::string, bool>& product_specific_data,
                   int remaining_ntps_to_open);
    explicit PendingTrigger(int remaining_ntps_to_open);
    ~PendingTrigger();

    std::map<std::string, bool> product_specific_data;
    int remaining_ntps_to_open;
    base::Time occurred_time;
  };

  // Class which observes the provided |web_contents| for |required_open_time|
  // and then checks if |web_contents| is currently visible, and has settings
  // open. Calls |success_callback| if the user stays on settings for the
  // required time, calls |complete_callback| when the observation time has
  // expired, or |web_contents| has been destroyed.
  class SettingsWatcher : content::WebContentsObserver {
   public:
    SettingsWatcher(content::WebContents* web_contents,
                    base::TimeDelta required_open_time,
                    base::OnceCallback<void()> success_callback,
                    base::OnceCallback<void()> complete_callback);
    ~SettingsWatcher() override;

    // WebContentsObserver:
    void WebContentsDestroyed() override;

   private:
    void TimerComplete();

    raw_ptr<content::WebContents> web_contents_;
    base::OnceCallback<void()> success_callback_;
    base::OnceCallback<void()> complete_callback_;
    base::WeakPtrFactory<SettingsWatcher> weak_ptr_factory_{this};
  };

  // Struct which represents the PageInfo state of interest to the service.
  struct PageInfoState {
    PageInfoState();
    base::Time opened_time;
    bool interacted = false;
  };

  // Struct which represents the PhishedPasswordChange state. When a user clicks
  // to change their password, we want to wait to trigger a survey until after
  // they change their password or the user has been inactive for some time.
  struct PhishedPasswordChangeState {
    PhishedPasswordChangeState();
    base::Time password_change_click_ts_;
    PasswordProtectionUIType ui_type_;
    bool finished_action = false;
  };

  void SettingsWatcherComplete();

  // Record that a trigger occurred, placing it in the set of pending triggers.
  // Private as the service itself determines when a trigger has occurred, and
  // is responsible for generating the appropriate |product_specific_data|.
  void TriggerOccurred(
      FeatureArea feature_area,
      const std::map<std::string, bool>& product_specific_data);

  // Record that the user performed an action which should make them temporarily
  // ineligible to receive a survey. This records a trigger for the kIneligible
  // feature area, which just like any other trigger will prevent a survey from
  // being shown, but will not result in a survey.
  void PerformedIneligibleAction();

  static bool ShouldBlockSurvey(const PendingTrigger& trigger);

  // Called by |ProtectResetOrCheckPasswordClicked| and
  // |PhishedPasswordUpdateNotClicked|. Triggers a survey if one has not already
  // been triggered for the user journey.
  void MaybeTriggerPasswordProtectionSurvey(PasswordProtectionUIType ui_type,
                                            PasswordProtectionUIAction action);

  const raw_ptr<Profile> profile_;
  std::map<FeatureArea, PendingTrigger> pending_triggers_;
  std::unique_ptr<SettingsWatcher> settings_watcher_;
  std::unique_ptr<PageInfoState> page_info_state_;
  std::unique_ptr<PhishedPasswordChangeState> phished_password_change_state_;
  base::ScopedMultiSourceObservation<Profile, ProfileObserver>
      observed_profiles_{this};
  bool performed_control_group_dice_roll_;
  base::ScopedObservation<metrics::DesktopSessionDurationTracker,
                          metrics::DesktopSessionDurationTracker::Observer>
      session_duration_observation_{this};
  base::WeakPtrFactory<TrustSafetySentimentService> weak_ptr_factory_{this};
};

#endif  // CHROME_BROWSER_UI_HATS_TRUST_SAFETY_SENTIMENT_SERVICE_H_
