// 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 "chrome/browser/ui/android/hats/hats_service_android.h"

#include <algorithm>
#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/android/resource_mapper.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/sessions/exit_type_service.h"
#include "chrome/browser/ui/android/hats/survey_client_android.h"
#include "chrome/browser/ui/android/hats/survey_ui_delegate_android.h"
#include "components/messages/android/message_wrapper.h"
#include "components/resources/android/theme_resources.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"

constexpr char kHatsShouldShowSurveyReasonAndroidHistogram[] =
    "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReasonAndroid";

HatsServiceAndroid::DelayedSurveyTask::DelayedSurveyTask(
    HatsServiceAndroid* hats_service,
    const std::string& trigger,
    content::WebContents* web_contents,
    const SurveyBitsData& product_specific_bits_data,
    const SurveyStringData& product_specific_string_data,
    NavigationBehavior navigation_behavior,
    base::OnceClosure success_callback,
    base::OnceClosure failure_callback,
    const std::optional<std::string>& supplied_trigger_id,
    const SurveyOptions& survey_options)
    : hats_service_(hats_service),
      trigger_(trigger),
      product_specific_bits_data_(product_specific_bits_data),
      product_specific_string_data_(product_specific_string_data),
      navigation_behavior_(navigation_behavior),
      success_callback_(std::move(success_callback)),
      failure_callback_(std::move(failure_callback)),
      supplied_trigger_id_(supplied_trigger_id),
      survey_options_(survey_options) {
  Observe(web_contents);
}

HatsServiceAndroid::DelayedSurveyTask::~DelayedSurveyTask() = default;

void HatsServiceAndroid::DelayedSurveyTask::Launch() {
  CHECK(web_contents());
  if (!web_contents() ||
      web_contents()->GetVisibility() != content::Visibility::VISIBLE) {
    if (!failure_callback_.is_null()) {
      std::move(failure_callback_).Run();
    }
    hats_service_->RemoveTask(*this);
    return;
  }

  message_ = std::make_unique<messages::MessageWrapper>(
      survey_options_.message_identifier.value_or(
          messages::MessageIdentifier::CHROME_SURVEY),
      std::move(success_callback_),
      base::BindOnce(&HatsServiceAndroid::DelayedSurveyTask::DismissCallback,
                     weak_ptr_factory_.GetWeakPtr()));

  if (survey_options_.custom_invitation.has_value()) {
    message_->SetTitle(survey_options_.custom_invitation.value());
  }

  ui::WindowAndroid* window_android = web_contents()->GetTopLevelNativeWindow();

  delegate_ = std::make_unique<hats::SurveyUiDelegateAndroid>(message_.get(),
                                                              window_android);

  // Create survey client with delegate.
  hats::SurveyClientAndroid survey_client(trigger_, delegate_.get(),
                                          hats_service_->profile(),
                                          supplied_trigger_id_, window_android);
  survey_client.LaunchSurvey(window_android, product_specific_bits_data_,
                             product_specific_string_data_);
  survey_launched_ = true;
}

void HatsServiceAndroid::DelayedSurveyTask::DismissCallback(
    messages::DismissReason dismiss_reason) {
  if (dismiss_reason != messages::DismissReason::PRIMARY_ACTION &&
      !failure_callback_.is_null()) {
    std::move(failure_callback_).Run();
  }

  ShouldShowSurveyReasonsAndroid reason;
  switch (dismiss_reason) {
    case messages::DismissReason::UNKNOWN:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidUnknown;
      break;
    case messages::DismissReason::PRIMARY_ACTION:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidAccepted;
      break;
    case messages::DismissReason::SECONDARY_ACTION:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidSecondaryAction;
      break;
    case messages::DismissReason::TIMER:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidExpired;
      break;
    case messages::DismissReason::GESTURE:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidDismissedByGesture;
      break;
    case messages::DismissReason::TAB_SWITCHED:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidTabSwitched;
      break;
    case messages::DismissReason::TAB_DESTROYED:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidTabDestroyed;
      break;
    case messages::DismissReason::ACTIVITY_DESTROYED:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidActivityDestroyed;
      break;
    case messages::DismissReason::SCOPE_DESTROYED:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidScopeDestroyed;
      break;
    case messages::DismissReason::DISMISSED_BY_FEATURE:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidDismissedByFeature;
      break;
    case messages::DismissReason::CLOSE_BUTTON:
      reason = ShouldShowSurveyReasonsAndroid::kAndroidCloseButton;
      break;
    case messages::DismissReason::COUNT:
      NOTREACHED();
  }
  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonAndroidHistogram,
                            reason);
  hats_service_->RemoveTask(*this);
}

void HatsServiceAndroid::DelayedSurveyTask::DidFinishNavigation(
    content::NavigationHandle* navigation_handle) {
  if (survey_launched_) {
    return;
  }

  if (hats_service_->IsNavigationAllowed(navigation_handle,
                                         navigation_behavior_)) {
    return;
  }

  if (!failure_callback_.is_null()) {
    std::move(failure_callback_).Run();
  }
  hats_service_->RemoveTask(*this);
}

void HatsServiceAndroid::DelayedSurveyTask::WebContentsDestroyed() {
  if (!failure_callback_.is_null()) {
    std::move(failure_callback_).Run();
  }
  hats_service_->RemoveTask(*this);
}

base::WeakPtr<HatsServiceAndroid::DelayedSurveyTask>
HatsServiceAndroid::DelayedSurveyTask::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

HatsServiceAndroid::HatsServiceAndroid(Profile* profile)
    : HatsService(profile) {}

HatsServiceAndroid::~HatsServiceAndroid() = default;

HatsService::LaunchError HatsServiceAndroid::LaunchSurvey(
    const std::string& trigger,
    base::OnceClosure success_callback,
    base::OnceClosure failure_callback,
    const SurveyBitsData& product_specific_bits_data,
    const SurveyStringData& product_specific_string_data,
    const std::optional<std::string>& supplied_trigger_id,
    const SurveyOptions& survey_options) {
  NOTIMPLEMENTED();
  return LaunchError::kError;
}

HatsService::LaunchError HatsServiceAndroid::LaunchSurveyForWebContents(
    const std::string& trigger,
    content::WebContents* web_contents,
    const SurveyBitsData& product_specific_bits_data,
    const SurveyStringData& product_specific_string_data,
    base::OnceClosure success_callback,
    base::OnceClosure failure_callback,
    const std::optional<std::string>& supplied_trigger_id,
    const SurveyOptions& survey_options) {
  // By using a delayed survey with a delay of 0, we can centralize the object
  // lifecycle management duties for native clank survey triggers.
  return LaunchDelayedSurveyForWebContents(
      trigger, web_contents, 0, product_specific_bits_data,
      product_specific_string_data, HatsService::NavigationBehavior::ALLOW_ANY,
      std::move(success_callback), std::move(failure_callback),
      supplied_trigger_id, survey_options);
}

HatsService::LaunchError HatsServiceAndroid::LaunchDelayedSurvey(
    const std::string& trigger,
    int timeout_ms,
    const SurveyBitsData& product_specific_bits_data,
    const SurveyStringData& product_specific_string_data) {
  NOTIMPLEMENTED();
  return LaunchError::kError;
}

HatsService::LaunchError HatsServiceAndroid::LaunchDelayedSurveyForWebContents(
    const std::string& trigger,
    content::WebContents* web_contents,
    int timeout_ms,
    const SurveyBitsData& product_specific_bits_data,
    const SurveyStringData& product_specific_string_data,
    NavigationBehavior navigation_behavior,
    base::OnceClosure success_callback,
    base::OnceClosure failure_callback,
    const std::optional<std::string>& supplied_trigger_id,
    const SurveyOptions& survey_options) {
  CHECK(web_contents);
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (survey_configs_by_triggers_.find(trigger) ==
      survey_configs_by_triggers_.end()) {
    // Survey configuration is not available.
    if (!failure_callback.is_null()) {
      std::move(failure_callback).Run();
    }
    return LaunchError::kNoTriggerConfig;
  }
  // Check for duplicate task before moving callbacks.
  auto duplicate_it =
      std::ranges::find_if(pending_tasks_, [&](const DelayedSurveyTask& task) {
        return task.web_contents() == web_contents && task.trigger() == trigger;
      });
  if (duplicate_it != pending_tasks_.end()) {
    if (!failure_callback.is_null()) {
      std::move(failure_callback).Run();
    }
    return LaunchError::kSurveyInProgress;
  }
  auto result = pending_tasks_.emplace(
      this, trigger, web_contents, product_specific_bits_data,
      product_specific_string_data, navigation_behavior,
      std::move(success_callback), std::move(failure_callback),
      supplied_trigger_id, survey_options);
  CHECK(result.second);
  auto success =
      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&HatsServiceAndroid::DelayedSurveyTask::Launch,
                         const_cast<HatsServiceAndroid::DelayedSurveyTask&>(
                             *(result.first))
                             .GetWeakPtr()),
          base::Milliseconds(timeout_ms));
  if (!success) {
    pending_tasks_.erase(result.first);
    return LaunchError::kError;
  }
  return LaunchError::kNone;
}

bool HatsServiceAndroid::CanShowAnySurvey(bool user_prompted) const {
  NOTIMPLEMENTED();  // Survey throttling happens on the clank side
  return false;
}

bool HatsServiceAndroid::CanShowSurvey(const std::string& trigger) const {
  NOTIMPLEMENTED();  // Survey throttling happens on the clank side
  return false;
}

void HatsServiceAndroid::RecordSurveyAsShown(std::string trigger_id) {
  // Record the trigger associated with the trigger_id. This is recorded
  // instead of the trigger ID itself, as the ID is specific to individual
  // survey versions. There should be a cooldown before a user is prompted to
  // take a survey from the same trigger, regardless of whether the survey was
  // updated.
  auto trigger_survey_config =
      std::ranges::find(survey_configs_by_triggers_, trigger_id,
                        [](const SurveyConfigs::value_type& pair) {
                          return pair.second.trigger_id;
                        });

  CHECK(trigger_survey_config != survey_configs_by_triggers_.end());
  std::string trigger = trigger_survey_config->first;

  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonAndroidHistogram,
                            ShouldShowSurveyReasonsAndroid::kYes);
}

void HatsServiceAndroid::RemoveTask(const DelayedSurveyTask& task) {
  pending_tasks_.erase(task);
}

bool HatsServiceAndroid::HasPendingTasksForTesting() {
  return !pending_tasks_.empty();
}
