// Copyright 2014 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/feedback/show_feedback_page.h"

#include <string>

#include "base/json/json_writer.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/escape.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "chrome/browser/feedback/feedback_dialog_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/webui/feedback/feedback_dialog.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/account_capabilities.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "extensions/browser/api/feedback_private/feedback_private_api.h"
#include "third_party/re2/src/re2/re2.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/webui/os_feedback_ui/url_constants.h"
#include "base/functional/bind.h"
#include "base/strings/strcat.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chromeos/ash/components/system_web_apps/system_web_app_type.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "google_apis/gaia/gaia_auth_util.h"
#endif

namespace feedback_private = extensions::api::feedback_private;

namespace chrome {

namespace {

#if BUILDFLAG(IS_CHROMEOS)
constexpr char kExtraDiagnosticsQueryParam[] = "extra_diagnostics";
constexpr char kDescriptionTemplateQueryParam[] = "description_template";
constexpr char kDescriptionPlaceholderQueryParam[] =
    "description_placeholder_text";
constexpr char kFromAssistantQueryParam[] = "from_assistant";
constexpr char kSettingsSearchDoNotRecordMetricsQueryParam[] =
    "settings_search_do_not_record_metrics";
constexpr char kCategoryTagParam[] = "category_tag";
constexpr char kPageURLParam[] = "page_url";
constexpr char kQueryParamSeparator[] = "&";
constexpr char kQueryParamKeyValueSeparator[] = "=";
constexpr char kFromAssistantQueryParamValue[] = "true";
constexpr char kSettingsSearchDoNotRecordMetricsQueryParamValue[] = "true";
constexpr char kFromAutofillQueryParam[] = "from_autofill";
constexpr char kFromAutofillParamValue[] = "true";
constexpr char kAutofillMetadataQueryParam[] = "autofill_metadata";

// Concat query parameter with escaped value.
std::string StrCatQueryParam(std::string_view query_param,
                             std::string_view value) {
  return base::StrCat({query_param, kQueryParamKeyValueSeparator,
                       base::EscapeQueryParamValue(value, /*use_plus=*/false)});
}

// Returns URL for OS Feedback with additional data passed as query parameters.
GURL BuildFeedbackUrl(const std::string& extra_diagnostics,
                      const std::string& description_template,
                      const std::string& description_placeholder_text,
                      const std::string& category_tag,
                      const GURL& page_url,
                      feedback::FeedbackSource source,
                      base::DictValue autofill_metadata) {
  std::vector<std::string> query_params;

  if (!extra_diagnostics.empty()) {
    query_params.emplace_back(
        StrCatQueryParam(kExtraDiagnosticsQueryParam, extra_diagnostics));
  }

  if (!description_template.empty()) {
    query_params.emplace_back(
        StrCatQueryParam(kDescriptionTemplateQueryParam, description_template));
  }

  if (!description_placeholder_text.empty()) {
    query_params.emplace_back(StrCatQueryParam(
        kDescriptionPlaceholderQueryParam, description_placeholder_text));
  }

  if (!category_tag.empty()) {
    query_params.emplace_back(
        StrCatQueryParam(kCategoryTagParam, category_tag));
  }

  if (!page_url.is_empty()) {
    query_params.emplace_back(StrCatQueryParam(kPageURLParam, page_url.spec()));
  }

  if (source == feedback::kFeedbackSourceAssistant) {
    query_params.emplace_back(StrCatQueryParam(kFromAssistantQueryParam,
                                               kFromAssistantQueryParamValue));
  }

  if (source == feedback::kFeedbackSourceOsSettingsSearch) {
    // If the user has queried for "fingerprint" in Settings app, we want to
    // check the 'Send system & app info and metrics' checkbox in the feedback
    // dialog.
    if (description_template.empty() ||
        !re2::RE2::PartialMatch(description_template, "fingerprint")) {
      query_params.emplace_back(
          StrCatQueryParam(kSettingsSearchDoNotRecordMetricsQueryParam,
                           kSettingsSearchDoNotRecordMetricsQueryParamValue));
    }
  }

  if (source == feedback::kFeedbackSourceAutofillContextMenu) {
    query_params.emplace_back(
        StrCatQueryParam(kFromAutofillQueryParam, kFromAutofillParamValue));

    std::string autofill_metadata_json =
        base::WriteJson(autofill_metadata).value_or("");
    query_params.emplace_back(
        StrCatQueryParam(kAutofillMetadataQueryParam, autofill_metadata_json));
  }

  // Use default URL if no extra parameters to be added.
  if (query_params.empty()) {
    return GURL(ash::kChromeUIOSFeedbackUrl);
  }

  return GURL(
      base::StrCat({ash::kChromeUIOSFeedbackUrl, "/?",
                    base::JoinString(query_params, kQueryParamSeparator)}));
}

// Returns whether the user has an internal Google account (e.g. @google.com).
bool IsGoogleInternalAccount(Profile* profile) {
  auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
  if (!identity_manager)  // Non-GAIA account, e.g. guest mode.
    return false;
  // Browser sync consent is not required to use feedback.
  CoreAccountInfo account_info =
      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
  return gaia::IsGoogleInternalAccountEmail(account_info.email);
}

// Returns if the feedback page is considered to be triggered from user
// interaction.
bool IsFromUserInteraction(feedback::FeedbackSource source) {
  switch (source) {
    case feedback::kFeedbackSourceArcApp:
    case feedback::kFeedbackSourceAsh:
    case feedback::kFeedbackSourceAssistant:
    case feedback::kFeedbackSourceAutofillContextMenu:
    case feedback::kFeedbackSourceBrowserCommand:
    case feedback::kFeedbackSourceConnectivityDiagnostics:
    case feedback::kFeedbackSourceDesktopTabGroups:
    case feedback::kFeedbackSourceCookieControls:
    case feedback::kFeedbackSourceNetworkHealthPage:
    case feedback::kFeedbackSourceMdSettingsAboutPage:
    case feedback::kFeedbackSourceOsSettingsSearch:
    case feedback::kFeedbackSourcePriceInsights:
    case feedback::kFeedbackSourceQuickAnswers:
    case feedback::kFeedbackSourceQuickOffice:
    case feedback::kFeedbackSourceSettingsPerformancePage:
      return true;
    default:
      return false;
  }
}

#endif  // BUILDFLAG(IS_CHROMEOS)

feedback_private::FeedbackFlow GetFeedbackFlowFromSource(
    feedback::FeedbackSource source) {
  switch (source) {
    case feedback::kFeedbackSourceSadTabPage:
      return feedback_private::FeedbackFlow::kSadTabCrash;
    case feedback::kFeedbackSourceAutofillContextMenu:
      return feedback_private::FeedbackFlow::kGoogleInternal;
    case feedback::kFeedbackSourceAI:
      return feedback_private::FeedbackFlow::kAi;
    default:
      return feedback_private::FeedbackFlow::kRegular;
  }
}

// Calls feedback private api to show Feedback ui.
void RequestFeedbackFlow(const GURL& page_url,
                         Profile* profile,
                         feedback::FeedbackSource source,
                         const std::string& description_template,
                         const std::string& description_placeholder_text,
                         const std::string& category_tag,
                         const std::string& extra_diagnostics,
                         base::DictValue autofill_metadata,
                         base::DictValue ai_metadata) {
  feedback_private::FeedbackFlow flow = GetFeedbackFlowFromSource(source);
  bool include_bluetooth_logs = false;
  bool show_questionnaire = false;
#if BUILDFLAG(IS_CHROMEOS)
  // TODO(crbug.com/40941303) Support ChromeOS feedback dialog for
  // `kFeedbackSourceAI`.
  if (source != feedback::kFeedbackSourceAI) {
    if (IsGoogleInternalAccount(profile)) {
      flow = feedback_private::FeedbackFlow::kGoogleInternal;
      include_bluetooth_logs = IsFromUserInteraction(source);
      show_questionnaire = IsFromUserInteraction(source);
    }

    if (!chromeos::IsKioskSession()) {
      ash::SystemAppLaunchParams params;
      params.url = BuildFeedbackUrl(
          extra_diagnostics, description_template, description_placeholder_text,
          category_tag, page_url, source, std::move(autofill_metadata));
      ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::OS_FEEDBACK,
                                   std::move(params));
      return;
    }
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  extensions::FeedbackPrivateAPI* api =
      extensions::FeedbackPrivateAPI::GetFactoryInstance()->Get(profile);
  auto info = api->CreateFeedbackInfo(
      description_template, description_placeholder_text, category_tag,
      extra_diagnostics, page_url, flow,
      source == feedback::kFeedbackSourceAssistant, include_bluetooth_logs,
      show_questionnaire, source == feedback::kFeedbackSourceChromeLabs,
      source == feedback::kFeedbackSourceAutofillContextMenu, autofill_metadata,
      ai_metadata);

  FeedbackDialog::CreateOrShow(profile, *info);
}

}  // namespace

bool CanShowFeedback(const Profile* profile) {
  if (!profile) {
    return false;
  }

  if (!profile->GetPrefs()->GetBoolean(prefs::kUserFeedbackAllowed)) {
    // Enterprise policy does not allow feedback.
    return false;
  }

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

  // Incognito profiles should apply the same restrictions as their original
  // profile.
  const Profile* original_profile = profile->GetOriginalProfile();
  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfileIfExists(original_profile);
  if (!identity_manager) {
    return true;
  }

  if (!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
    return true;
  }

  AccountInfo account_info = identity_manager->FindExtendedAccountInfo(
      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin));
  return account_info.GetAccountCapabilities().can_submit_feedback() !=
         signin::Tribool::kFalse;
#else
  // TODO(crbug.com/495657977): add ChromeOS implementation.
  return true;
#endif
}

void ShowFeedbackPage(BrowserWindowInterface* bwi,
                      feedback::FeedbackSource source,
                      const std::string& description_template,
                      const std::string& description_placeholder_text,
                      const std::string& category_tag,
                      const std::string& extra_diagnostics,
                      base::DictValue autofill_metadata,
                      base::DictValue ai_metadata) {
  GURL page_url;
  if (bwi) {
    page_url = GetTargetTabUrl(bwi, bwi->GetTabStripModel()->active_index());
  }

  Profile* profile = GetFeedbackProfile(bwi);

  ShowFeedbackPage(page_url, profile, source, description_template,
                   description_placeholder_text, category_tag,
                   extra_diagnostics, std::move(autofill_metadata),
                   std::move(ai_metadata));
}

void ShowFeedbackPage(const GURL& page_url,
                      Profile* profile,
                      feedback::FeedbackSource source,
                      const std::string& description_template,
                      const std::string& description_placeholder_text,
                      const std::string& category_tag,
                      const std::string& extra_diagnostics,
                      base::DictValue autofill_metadata,
                      base::DictValue ai_metadata) {
  if (!profile) {
    LOG(ERROR) << "Cannot invoke feedback: No profile found!";
    return;
  }
  if (!CanShowFeedback(profile)) {
    base::UmaHistogramEnumeration("Feedback.NotAllowed.RequestSource", source,
                                  feedback::kFeedbackSourceCount);
    return;
  }

  // Record an UMA histogram to know the most frequent feedback request source.
  UMA_HISTOGRAM_ENUMERATION("Feedback.RequestSource", source,
                            feedback::kFeedbackSourceCount);

  // Show feedback dialog using feedback extension API.
  RequestFeedbackFlow(page_url, profile, source, description_template,
                      description_placeholder_text, category_tag,
                      extra_diagnostics, std::move(autofill_metadata),
                      std::move(ai_metadata));
}

}  // namespace chrome
