// Copyright 2025 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/enterprise/browser/reporting/user_security_signals_service.h"

#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "components/device_signals/core/common/signals_features.h"
#include "components/enterprise/browser/reporting/common_pref_names.h"
#include "components/enterprise/browser/reporting/report_scheduler.h"
#include "components/enterprise/browser/reporting/report_util.h"
#include "components/enterprise/browser/reporting/reporting_features.h"
#include "components/policy/core/common/policy_logger.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/cookies/cookie_change_dispatcher.h"

namespace enterprise_reporting {

UserSecuritySignalsService::UserSecuritySignalsService(
    PrefService* profile_prefs,
    Delegate* delegate,
    policy::PolicyService* policy_service)
    : profile_prefs_(profile_prefs),
      delegate_(delegate),
      policy_service_(policy_service) {
  CHECK(profile_prefs_);
  CHECK(delegate_);
  CHECK(policy_service_);
}

UserSecuritySignalsService::~UserSecuritySignalsService() {
  StopPolicyObservation();
}

base::TimeDelta UserSecuritySignalsService::GetSecurityUploadCadence() {
  return enterprise_signals::features::kProfileSignalsReportingInterval.Get();
}

void UserSecuritySignalsService::Start() {
  if (initialized_) {
    return;
  }

  initialized_ = true;

  pref_change_registrar_.Init(profile_prefs_);
  pref_change_registrar_.Add(
      kUserSecuritySignalsReporting,
      base::BindRepeating(
          &UserSecuritySignalsService::OnStatePolicyValueChanged,
          weak_factory_.GetWeakPtr(), /*is_initialization=*/false));
  pref_change_registrar_.Add(
      kUserSecurityAuthenticatedReporting,
      base::BindRepeating(
          &UserSecuritySignalsService::OnCookiePolicyValueChanged,
          weak_factory_.GetWeakPtr()));

  // Manually trigger a policy update to initialize things.
  OnStatePolicyValueChanged(/*is_initialization=*/true);
}

bool UserSecuritySignalsService::IsSecuritySignalsReportingEnabled() {
  return profile_prefs_->GetBoolean(kUserSecuritySignalsReporting);
}

bool UserSecuritySignalsService::ShouldUseCookies() {
  return IsSecuritySignalsReportingEnabled() &&
         profile_prefs_->GetBoolean(kUserSecurityAuthenticatedReporting);
}

void UserSecuritySignalsService::OnReportUploaded() {
  timer_.Start(FROM_HERE, base::Time::Now() + GetSecurityUploadCadence(),
               base::BindRepeating(&UserSecuritySignalsService::TriggerReport,
                                   weak_factory_.GetWeakPtr(),
                                   SecurityReportTrigger::kTimer));
}

void UserSecuritySignalsService::OnCookieChange(
    const net::CookieChangeInfo& change) {
  if (!enterprise_signals::features::kTriggerOnCookieChange.Get()) {
    return;
  }
  // Only trigger a new upload if the cookie change could result in a new or
  // updated session.
  if (change.cause == net::CookieChangeCause::INSERTED) {
    TriggerReport(SecurityReportTrigger::kCookieChange);
  }
}

void UserSecuritySignalsService::InitCookieListener() {
  auto* cookie_manager = delegate_->GetCookieManager();
  if (!cookie_manager) {
    return;
  }

  // Only interested in the secure first-party authentication cookie.
  cookie_manager->AddCookieChangeListener(
      GaiaUrls::GetInstance()->secure_google_url(),
      GaiaConstants::kGaiaSigninCookieName,
      cookie_listener_receiver_.BindNewPipeAndPassRemote());

  cookie_listener_receiver_.set_disconnect_handler(base::BindOnce(
      &UserSecuritySignalsService::OnCookieListenerConnectionError,
      base::Unretained(this)));
}

void UserSecuritySignalsService::OnCookieListenerConnectionError() {
  // A connection error from the CookieManager likely means that the network
  // service process has crashed. Try again to set up a listener.
  cookie_listener_receiver_.reset();
  InitCookieListener();
}

void UserSecuritySignalsService::OnPolicyUpdated(
    const policy::PolicyNamespace& ns,
    const policy::PolicyMap& previous,
    const policy::PolicyMap& current) {
  if (!enterprise_signals::features::IsPolicyDataCollectionEnabled()) {
    return;
  }

  TriggerReport(SecurityReportTrigger::kPolicyChange);
}

void UserSecuritySignalsService::OnStatePolicyValueChanged(
    bool is_initialization) {
  if (!IsSecuritySignalsReportingEnabled()) {
    timer_.Stop();
    StopPolicyObservation();
    return;
  }

  if (timer_.IsRunning()) {
    return;
  }

  // Make sure that we are observing the policy service.
  StartPolicyObservation();

  // Make sure that cookie observation is properly set-up, as needed.
  OnCookiePolicyValueChanged();

  if (base::FeatureList::IsEnabled(kUploadReportOnProfileOpen) &&
      is_initialization) {
    // Skip sending a report at start-up.
    OnReportUploaded();
    return;
  }

  TriggerReport(SecurityReportTrigger::kTimer);
}

void UserSecuritySignalsService::OnCookiePolicyValueChanged() {
  if (ShouldUseCookies() && !cookie_listener_receiver_.is_bound()) {
    InitCookieListener();
  } else if (!ShouldUseCookies() && cookie_listener_receiver_.is_bound()) {
    cookie_listener_receiver_.reset();
  }
}

void UserSecuritySignalsService::TriggerReport(SecurityReportTrigger trigger) {
  VLOG_POLICY(1, REPORTING) << "Security signals report is triggered by "
                            << static_cast<int>(trigger);
  base::UmaHistogramEnumeration("Enterprise.SecurityReport.User.Trigger",
                                trigger);

  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&Delegate::OnReportEventTriggered,
                                base::Unretained(delegate_), trigger));
}

void UserSecuritySignalsService::StartPolicyObservation() {
  if (enterprise_signals::features::IsPolicyDataCollectionEnabled() &&
      !is_observing_policy_service_) {
    policy_service_->AddObserver(policy::POLICY_DOMAIN_CHROME, this);
    is_observing_policy_service_ = true;
  }
}

void UserSecuritySignalsService::StopPolicyObservation() {
  if (policy_service_ &&
      enterprise_signals::features::IsPolicyDataCollectionEnabled() &&
      is_observing_policy_service_) {
    policy_service_->RemoveObserver(policy::POLICY_DOMAIN_CHROME, this);
    is_observing_policy_service_ = false;
  }
}

}  // namespace enterprise_reporting
