// Copyright 2020 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/supervised_user/supervised_user_extensions_delegate_impl.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/supervised_user/extension_icon_loader.h"
#include "chrome/browser/supervised_user/supervised_user_browser_utils.h"
#include "chrome/browser/supervised_user/supervised_user_extensions_manager.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "chrome/browser/ui/supervised_user/extension_install_blocked_by_parent_dialog.h"
#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
#include "components/prefs/pref_service.h"
#include "components/supervised_user/core/common/features.h"
#include "components/supervised_user/core/common/pref_names.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_dialog_auto_confirm.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/native_ui_types.h"

#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/supervised_user/android/extension_parent_approval.h"
#endif  // BUILDFLAG(IS_ANDROID)

namespace {

void OnParentPermissionDialogComplete(
    extensions::SupervisedUserExtensionsDelegate::ExtensionApprovalDoneCallback
        delegate_done_callback,
    ParentPermissionDialog::Result result) {
  switch (result) {
    case ParentPermissionDialog::Result::kParentPermissionReceived:
      std::move(delegate_done_callback)
          .Run(extensions::SupervisedExtensionApprovalResult::kApproved);
      break;
    case ParentPermissionDialog::Result::kParentPermissionCanceled:
      std::move(delegate_done_callback)
          .Run(extensions::SupervisedExtensionApprovalResult::kCanceled);
      break;
    case ParentPermissionDialog::Result::kParentPermissionFailed:
      std::move(delegate_done_callback)
          .Run(extensions::SupervisedExtensionApprovalResult::kFailed);
      break;
  }
}

// Converts delegate enum to metrics recorder enum for AskParentDialogState.
SupervisedUserExtensionsMetricsRecorder::AskParentDialogState
ConvertAskParentDialogState(
    extensions::SupervisedUserExtensionsDelegate::AskParentDialogState state) {
  using DelegateState =
      extensions::SupervisedUserExtensionsDelegate::AskParentDialogState;
  using MetricsState =
      SupervisedUserExtensionsMetricsRecorder::AskParentDialogState;

  switch (state) {
    case DelegateState::kOpened:
      return MetricsState::kOpened;
    case DelegateState::kCanceled:
      return MetricsState::kCanceled;
    case DelegateState::kApproved:
      return MetricsState::kApproved;
  }
}

// Converts delegate enum to metrics recorder enum for EnablementState.
SupervisedUserExtensionsMetricsRecorder::EnablementState ConvertEnablementState(
    extensions::SupervisedUserExtensionsDelegate::EnablementState state) {
  using DelegateState =
      extensions::SupervisedUserExtensionsDelegate::EnablementState;
  using MetricsState = SupervisedUserExtensionsMetricsRecorder::EnablementState;

  switch (state) {
    case DelegateState::kEnabled:
      return MetricsState::kEnabled;
    case DelegateState::kDisabled:
      return MetricsState::kDisabled;
    case DelegateState::kFailedToEnable:
      return MetricsState::kFailedToEnable;
  }
}

}  // namespace

namespace extensions {

SupervisedUserExtensionsDelegateImpl::SupervisedUserExtensionsDelegateImpl(
    content::BrowserContext* browser_context)
    : context_(browser_context), extensions_manager_(context_) {
  CHECK(context_);
}

SupervisedUserExtensionsDelegateImpl::~SupervisedUserExtensionsDelegateImpl() =
    default;

void SupervisedUserExtensionsDelegateImpl::
    UpdateManagementPolicyRegistration() {
  extensions_manager_.UpdateManagementPolicyRegistration();
}

bool SupervisedUserExtensionsDelegateImpl::IsChild() const {
  auto* profile = Profile::FromBrowserContext(context_);
  return profile && supervised_user::AreExtensionsPermissionsEnabled(profile);
}

bool SupervisedUserExtensionsDelegateImpl::IsExtensionAllowedByParent(
    const Extension& extension) const {
  return extensions_manager_.IsExtensionAllowed(extension);
}

void SupervisedUserExtensionsDelegateImpl::RequestToAddExtensionOrShowError(
    const Extension& extension,
    content::WebContents* web_contents,
    const gfx::ImageSkia& icon,
    ExtensionApprovalDoneCallback extension_approval_callback) {
  CHECK(IsChild());

  done_callback_ = std::move(extension_approval_callback);
  RequestExtensionApproval(extension, web_contents->GetWeakPtr(), icon);
}

void SupervisedUserExtensionsDelegateImpl::RequestToEnableExtensionOrShowError(
    const Extension& extension,
    content::WebContents* web_contents,
    ExtensionApprovalDoneCallback extension_approval_callback) {
  CHECK(IsChild());
  CHECK(!IsExtensionAllowedByParent(extension));

  done_callback_ = std::move(extension_approval_callback);

  // Fetch icon from local resources and then show enable flow.
  auto icon_callback = base::BindOnce(
      &SupervisedUserExtensionsDelegateImpl::RequestExtensionApproval,
      base::Unretained(this), std::cref(extension),
      web_contents ? std::make_optional(web_contents->GetWeakPtr())
                   : std::nullopt);
  icon_loader_ = std::make_unique<ExtensionIconLoader>();
  icon_loader_->Load(extension, context_, std::move(icon_callback));
}

bool SupervisedUserExtensionsDelegateImpl::CanInstallExtensions() const {
  return extensions_manager_.CanInstallExtensions();
}

void SupervisedUserExtensionsDelegateImpl::AddExtensionApproval(
    const extensions::Extension& extension) {
  extensions_manager_.AddExtensionApproval(extension);
}

void SupervisedUserExtensionsDelegateImpl::
    MaybeRecordPermissionsIncreaseMetrics(
        const extensions::Extension& extension) {
  extensions_manager_.MaybeRecordPermissionsIncreaseMetrics(extension);
}

void SupervisedUserExtensionsDelegateImpl::RemoveExtensionApproval(
    const extensions::Extension& extension) {
  extensions_manager_.RemoveExtensionApproval(extension);
}

void SupervisedUserExtensionsDelegateImpl::RecordExtensionEnablementUmaMetrics(
    bool enabled) const {
  extensions_manager_.RecordExtensionEnablementUmaMetrics(enabled);
}

bool SupervisedUserExtensionsDelegateImpl::CanSkipExtensionParentApprovals() {
  return supervised_user::SupervisedUserCanSkipExtensionParentApprovals(
      Profile::FromBrowserContext(context_));
}

void SupervisedUserExtensionsDelegateImpl::RecordAskParentDialogUmaMetrics(
    AskParentDialogState state) {
  metrics_recorder_.RecordAskParentDialogUmaMetrics(
      ConvertAskParentDialogState(state));
}

void SupervisedUserExtensionsDelegateImpl::RecordEnablementUmaMetrics(
    EnablementState state) {
  metrics_recorder_.RecordEnablementUmaMetrics(ConvertEnablementState(state));
}

extensions::ExtensionInstallPromptClient::Observer*
SupervisedUserExtensionsDelegateImpl::GetInstallPromptObserver() {
  return &metrics_recorder_;
}

#if BUILDFLAG(IS_CHROMEOS)
void SupervisedUserExtensionsDelegateImpl::
    SetParentAccessExtensionApprovalsManagerForTesting(
        std::unique_ptr<ParentAccessExtensionApprovalsManager> manager) {
  extension_approvals_manager_ = std::move(manager);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

void SupervisedUserExtensionsDelegateImpl::
    ShowParentPermissionDialogForExtension(const Extension& extension,
                                           content::WebContents* contents,
                                           const gfx::ImageSkia& icon) {
  ParentPermissionDialog::DoneCallback inner_done_callback = base::BindOnce(
      &::OnParentPermissionDialogComplete, std::move(done_callback_));

  gfx::NativeWindow parent_window =
      contents ? contents->GetTopLevelNativeWindow() : gfx::NativeWindow();
  parent_permission_dialog_ =
      ParentPermissionDialog::CreateParentPermissionDialogForExtension(
          Profile::FromBrowserContext(context_), parent_window, icon,
          &extension, std::move(inner_done_callback));
  parent_permission_dialog_->ShowDialog();
}

void SupervisedUserExtensionsDelegateImpl::
    ShowInstallBlockedByParentDialogForExtension(
        const Extension& extension,
        content::WebContents* contents,
        ExtensionInstalledBlockedByParentDialogAction blocked_action) {
  auto block_dialog_callback = base::BindOnce(
      std::move(done_callback_), SupervisedExtensionApprovalResult::kBlocked);
  metrics_recorder_.RecordEnablementUmaMetrics(
      SupervisedUserExtensionsMetricsRecorder::EnablementState::
          kFailedToEnable);
  if (ScopedTestDialogAutoConfirm::GetAutoConfirmValue() !=
      ScopedTestDialogAutoConfirm::NONE) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(block_dialog_callback));
    return;
  }
  ShowExtensionInstallBlockedByParentDialog(
      ExtensionInstalledBlockedByParentDialogAction::kEnable, &extension,
      contents, std::move(block_dialog_callback));
}

void SupervisedUserExtensionsDelegateImpl::RequestExtensionApproval(
    const Extension& extension,
    std::optional<base::WeakPtr<content::WebContents>> contents,
    const gfx::ImageSkia& icon) {
  // Treat the request as canceled if web contents that the request originated
  // in was destroyed (the web contents was originally passed, but weak ptr is
  // not valid anymore).
  if (contents) {
    base::WeakPtr<content::WebContents> contents_weak_ptr = contents.value();
    if (!contents_weak_ptr) {
      std::move(done_callback_)
          .Run(SupervisedExtensionApprovalResult::kCanceled);
      return;
    }
  }
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
  CHECK(contents.value());
  content::WebContents* web_contents = contents.value().get();
  // Always invoke the parent permission dialog.
  ShowParentPermissionDialogForExtension(extension, web_contents, icon);
  return;
#elif BUILDFLAG(IS_CHROMEOS)
  // ParentAccessDialog handles the blocked use case for ChromeOS.
  if (!extension_approvals_manager_) {
    extension_approvals_manager_ =
        std::make_unique<ParentAccessExtensionApprovalsManager>();
  }
  extension_approvals_manager_->ShowParentAccessDialog(
      extension, context_, icon,
      CanInstallExtensions() ? ParentAccessExtensionApprovalsManager::
                                   ExtensionInstallMode::kInstallationPermitted
                             : ParentAccessExtensionApprovalsManager::
                                   ExtensionInstallMode::kInstallationDenied,
      std::move(done_callback_));
#elif BUILDFLAG(IS_ANDROID)
  CHECK(contents.value());
  ExtensionParentApproval::RequestExtensionApproval(contents.value().get(),
                                                    std::move(done_callback_));
#endif
}

}  // namespace extensions
