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

#include "chrome/browser/ash/system/system_clock.h"

#include <memory>

#include "ash/constants/ash_pref_names.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "chrome/browser/ash/ownership/owner_settings_service_ash.h"
#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/system/system_clock_observer.h"
#include "chromeos/ash/components/browser_context_helper/annotated_account_id.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "components/user_manager/user_type.h"

namespace ash::system {

SystemClock::SystemClock() {
  device_settings_observer_ = CrosSettings::Get()->AddSettingsObserver(
      kSystemUse24HourClock,
      base::BindRepeating(&SystemClock::OnSystemPrefChanged,
                          weak_ptr_factory_.GetWeakPtr()));

  user_manager::UserManager::Get()->AddSessionStateObserver(this);
}

SystemClock::~SystemClock() {
  if (user_manager::UserManager::IsInitialized())
    user_manager::UserManager::Get()->RemoveSessionStateObserver(this);
}

void SystemClock::OnProfileWillBeDestroyed(Profile* profile) {
  DCHECK(profile_observation_.IsObservingSource(profile));
  profile_observation_.Reset();
  user_pref_registrar_.reset();
}

void SystemClock::ActiveUserChanged(user_manager::User* active_user) {
  if (!active_user)
    return;

  active_user->AddProfileCreatedObserver(
      base::BindOnce(&SystemClock::SetProfileByUser,
                     weak_ptr_factory_.GetWeakPtr(), active_user));
}

void SystemClock::AddObserver(SystemClockObserver* observer) {
  observer_list_.AddObserver(observer);
}

void SystemClock::RemoveObserver(SystemClockObserver* observer) {
  observer_list_.RemoveObserver(observer);
}

void SystemClock::SetProfileByUser(const user_manager::User* user) {
  SetProfile(ProfileHelper::Get()->GetProfileByUser(user));
}

void SystemClock::SetProfile(Profile* profile) {
  profile_observation_.Reset();
  profile_observation_.Observe(profile);
  PrefService* prefs = profile->GetPrefs();
  user_pref_registrar_ = std::make_unique<PrefChangeRegistrar>();
  user_pref_registrar_->Init(prefs);
  user_pref_registrar_->Add(ash::prefs::kUse24HourClock,
                            base::BindRepeating(&SystemClock::OnUserPrefChanged,
                                                base::Unretained(this)));
  NotifySystemClockTypeChanged();
}

SystemClock::ScopedHourClockType::ScopedHourClockType(
    base::WeakPtr<SystemClock> system_clock)
    : system_clock_(std::move(system_clock)) {}

SystemClock::ScopedHourClockType::~ScopedHourClockType() {
  if (!system_clock_)
    return;
  system_clock_->scoped_hour_clock_type_.reset();
  system_clock_->NotifySystemClockTypeChanged();
}

SystemClock::ScopedHourClockType::ScopedHourClockType(
    ScopedHourClockType&& other) = default;

SystemClock::ScopedHourClockType& SystemClock::ScopedHourClockType::operator=(
    ScopedHourClockType&& other) = default;

void SystemClock::ScopedHourClockType::UpdateClockType(
    base::HourClockType clock_type) {
  if (!system_clock_)
    return;
  system_clock_->scoped_hour_clock_type_ = clock_type;
  system_clock_->NotifySystemClockTypeChanged();
}

SystemClock::ScopedHourClockType SystemClock::CreateScopedHourClockType(
    base::HourClockType hour_clock_type) {
  DCHECK(!scoped_hour_clock_type_.has_value());
  scoped_hour_clock_type_ = hour_clock_type;
  NotifySystemClockTypeChanged();
  return ScopedHourClockType(weak_ptr_factory_.GetWeakPtr());
}

bool SystemClock::ShouldUse24HourClockForUser(const AccountId& user_id) const {
  // System value is used for kUse24HourClock preference on login screen and
  // whenever set so in user's preference
  const CrosSettings* const cros_settings = CrosSettings::Get();
  bool system_use_24_hour_clock = true;
  const bool system_value_found = cros_settings->GetBoolean(
      kSystemUse24HourClock, &system_use_24_hour_clock);
  const bool system_value =
      system_value_found ? system_use_24_hour_clock
                         : (base::GetHourClockType() == base::k24HourClock);

  const user_manager::User* const user =
      user_manager::UserManager::Get()->FindUser(user_id);
  if (!user || !user->GetProfilePrefs()) {
    return system_value;
  }

  const PrefService::Preference* user_pref =
      user->GetProfilePrefs()->FindPreference(ash::prefs::kUse24HourClock);
  if (user_pref->IsDefaultValue()) {
    // Non-regular users or owner use `system_value`. Owner uses `system_value`
    // to mitigate the edge case where owner data is lost because `system_value`
    // should always be consistent with the (lost) owner data.
    if (user->GetType() != user_manager::UserType::kRegular ||
        user_manager::UserManager::Get()->IsOwnerUser(user)) {
      return system_value;
    }

    // Regular users use `system_value` on managed devices where `system_value`
    // is from device policy.
    DCHECK_EQ(user->GetType(), user_manager::UserType::kRegular);
    if (ash::InstallAttributes::Get()->IsEnterpriseManaged()) {
      return system_value;
    }
  }

  return user_pref->GetValue()->GetIfBool().value_or(true);
}

bool SystemClock::ShouldUse24HourClock() const {
  if (scoped_hour_clock_type_.has_value()) {
    return scoped_hour_clock_type_ == base::k24HourClock;
  }

  user_manager::User* const active_user =
      user_manager::UserManager::Get()->GetActiveUser();

  return ShouldUse24HourClockForUser(active_user ? active_user->GetAccountId()
                                                 : EmptyAccountId());
}

void SystemClock::OnSystemPrefChanged() {
  NotifySystemClockTypeChanged();
}

void SystemClock::OnUserPrefChanged() {
  // It apparently sometimes takes a while after login before the current user
  // is recognized as the owner. Make sure that the system-wide clock setting
  // is updated when the recognition eventually happens (crbug.com/41040754).
  // This also works for enterprise-managed devices because they never have
  // a local owner.
  const AccountId* user_id =
      ash::AnnotatedAccountId::Get(profile_observation_.GetSource());
  if (user_id) {
    user_manager::UserManager::Get()->GetOwnerAccountIdAsync(
        base::BindOnce(&SystemClock::MaybeCopyToOwnerSettings,
                       weak_ptr_factory_.GetWeakPtr(), *user_id));
  }

  NotifySystemClockTypeChanged();
}

void SystemClock::MaybeCopyToOwnerSettings(const AccountId& updating_user_id,
                                           const AccountId& owner_id) {
  if (updating_user_id != owner_id) {
    // Non owner user.
    return;
  }

  const bool use_24_hour_clock = ShouldUse24HourClockForUser(updating_user_id);

  const user_manager::User* const user =
      user_manager::UserManager::Get()->FindUser(updating_user_id);
  if (!user) {
    return;  // May occur if not running on a device.
  }
  Profile* const profile = ProfileHelper::Get()->GetProfileByUser(user);
  if (!profile) {
    return;  // May occur in tests or if not running on a device.
  }
  OwnerSettingsServiceAsh* const service =
      OwnerSettingsServiceAshFactory::GetForBrowserContext(profile);
  CHECK(service);
  service->SetBoolean(kSystemUse24HourClock, use_24_hour_clock);
}

void SystemClock::NotifySystemClockTypeChanged() {
  for (auto& observer : observer_list_)
    observer.OnSystemClockChanged(this);
}

}  // namespace ash::system
