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

#include "components/user_education/common/session/user_education_session_manager.h"

#include <utility>

#include "base/callback_list.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/time/time.h"
#include "components/user_education/common/session/user_education_idle_observer.h"
#include "components/user_education/common/session/user_education_idle_policy.h"
#include "components/user_education/common/user_education_features.h"
#include "components/user_education/common/user_education_storage_service.h"

namespace user_education {

UserEducationSessionManager::UserEducationSessionManager() = default;
UserEducationSessionManager::~UserEducationSessionManager() = default;

void UserEducationSessionManager::Init(
    UserEducationStorageService* storage_service,
    std::unique_ptr<UserEducationIdleObserver> idle_observer,
    std::unique_ptr<UserEducationIdlePolicy> idle_policy) {
  storage_service_ = storage_service;
  idle_policy_ = std::move(idle_policy);
  idle_policy_->Init(this, storage_service_.get());
  // Assume the application is active at application start; this avoids making
  // additional system calls during startup.
  UpdateLastActiveTime(storage_service_->GetCurrentTime());
  // Start observing state.
  SetIdleObserver(std::move(idle_observer));
}

void UserEducationSessionManager::MaybeUpdateSessionState() {
  if (!storage_service_) {
    return;
  }

  // Determine if a new session could be started.
  const auto old_state = storage_service_->ReadSessionData();
  const auto now = storage_service_->GetCurrentTime();
  if (!idle_policy_->IsNewSession(old_state.start_time,
                                  old_state.most_recent_active_time, now)) {
    return;
  }

  const auto last_active = idle_observer_->MaybeGetNewLastActiveTime();
  if (last_active) {
    UpdateLastActiveTime(*last_active);
  }
}

base::CallbackListSubscription
UserEducationSessionManager::AddNewSessionCallback(
    base::RepeatingClosure new_session_callback) {
  return new_session_callbacks_.Add(std::move(new_session_callback));
}

bool UserEducationSessionManager::GetNewSessionSinceStartup() const {
  return new_session_since_startup_;
}

void UserEducationSessionManager::OnNewSession(
    const base::Time old_start_time,
    const base::Time old_active_time,
    const base::Time new_active_time) {
  new_session_since_startup_ = true;

  base::RecordAction(
      base::UserMetricsAction("UserEducation.Session.ActivePeriodStart"));

  // Now starting new session. The Active Period of the old session
  // is the difference between old session times.
  RecordActivePeriodDuration(old_active_time - old_start_time);

  // The now-elapsed Idle Period is difference between now and the
  // previous most_recent_active_time.
  RecordIdlePeriodDuration(new_active_time - old_active_time);

  // Notify any listeners of the new session.
  new_session_callbacks_.Notify();
}

void UserEducationSessionManager::OnLastActiveTimeUpdating(base::Time) {}

void UserEducationSessionManager::SetIdleObserver(
    std::unique_ptr<UserEducationIdleObserver> new_observer) {
  idle_observer_ = std::move(new_observer);
  idle_observer_->Init(storage_service_.get());
  idle_observer_subscription_ = idle_observer_->AddUpdateCallback(
      base::BindRepeating(&UserEducationSessionManager::UpdateLastActiveTime,
                          base::Unretained(this)));
  idle_observer_->StartObserving();
}

void UserEducationSessionManager::RecordActivePeriodDuration(
    base::TimeDelta duration) {
  // Increments of 1 minute under 1 hour.
  base::UmaHistogramCustomCounts(
      "UserEducation.Session.ActivePeriodDuration.Min.Under1Hour",
      duration.InMinutes(), /*min=*/1,
      /*exclusive_max=*/60,
      /*buckets=*/60);

  // Increments of 15 minutes under 24 hours.
  base::UmaHistogramCustomCounts(
      "UserEducation.Session.ActivePeriodDuration.Min.Under24Hours",
      duration.InMinutes(), /*min=*/1,
      /*exclusive_max=*/60 * 24 /* minutes per 24 hours */,
      /*buckets=*/24 * 4 /* per 15 minutes */);
}

void UserEducationSessionManager::RecordIdlePeriodDuration(
    base::TimeDelta duration) {
  // Increments of 15 minutes under 24 hours.
  base::UmaHistogramCustomCounts(
      "UserEducation.Session.IdlePeriodDuration.Min.Under24Hours",
      duration.InMinutes(), /*min=*/1,
      /*exclusive_max=*/60 * 24 /* minutes per 24 hours */,
      /*buckets=*/24 * 4 /* per 15 minute */);

  // Increments of ~13 hours under 28 days.
  base::UmaHistogramCustomCounts(
      "UserEducation.Session.IdlePeriodDuration.Hr.Under28Days",
      duration.InHours(), /*min=*/1,
      /*exclusive_max=*/24 * 28 /* hours per 28 days */,
      /*buckets=*/50);
}

void UserEducationSessionManager::UpdateLastActiveTime(
    base::Time new_active_time) {
  CHECK(idle_policy_);
  OnLastActiveTimeUpdating(new_active_time);

  auto session_data = storage_service_->ReadSessionData();
  const auto old_start_time = session_data.start_time;
  const auto old_active_time = session_data.most_recent_active_time;
  session_data.most_recent_active_time = new_active_time;
  const bool is_new_session = idle_policy_->IsNewSession(
      old_start_time, old_active_time, new_active_time);
  if (is_new_session) {
    session_data.start_time = new_active_time;
    ++session_data.session_number;
  }
  // Save the session data before calling OnNewSession, since some listeners
  // will be relying on the data being current.
  storage_service_->SaveSessionData(session_data);
  if (is_new_session) {
    OnNewSession(old_start_time, old_active_time, new_active_time);
  }
}

}  // namespace user_education
