// 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/web_applications/commands/navigate_and_trigger_install_dialog_command.h"

#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/to_string.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/commands/web_app_command.h"
#include "chrome/browser/web_applications/locks/app_lock.h"
#include "chrome/browser/web_applications/locks/noop_lock.h"
#include "chrome/browser/web_applications/locks/web_app_lock_manager.h"
#include "chrome/browser/web_applications/scheduler/navigate_and_trigger_install_dialog_result.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_ui_manager.h"
#include "chrome/browser/web_applications/web_contents/web_app_data_retriever.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_logging.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/browser/web_contents/web_app_url_loader.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace web_app {

NavigateAndTriggerInstallDialogCommand::NavigateAndTriggerInstallDialogCommand(
    const GURL& install_url,
    const GURL& origin_url,
    bool is_renderer_initiated,
    webapps::WebappInstallSource source,
    NavigateAndTriggerInstallDialogCommandCallback callback,
    base::WeakPtr<WebAppUiManager> ui_manager,
    std::unique_ptr<webapps::WebAppUrlLoader> url_loader,
    std::unique_ptr<WebAppDataRetriever> data_retriever,
    Profile* profile)
    : WebAppCommand<NoopLock, NavigateAndTriggerInstallDialogResult>(
          "NavigateAndTriggerInstallDialogCommand",
          NoopLockDescription(),
          std::move(callback),
          /*args_for_shutdown=*/
          NavigateAndTriggerInstallDialogResult::kShutdown),
      install_url_(install_url),
      origin_url_(origin_url),
      is_renderer_initiated_(is_renderer_initiated),
      source_(source),
      ui_manager_(ui_manager),
      url_loader_(std::move(url_loader)),
      data_retriever_(std::move(data_retriever)),
      profile_(profile) {
  CHECK(url_loader_);
  CHECK(data_retriever_);
  CHECK(profile_);
  GetMutableDebugValue().Set("install_url", install_url_.spec());
  GetMutableDebugValue().Set("origin_url", origin_url_.spec());
  GetMutableDebugValue().Set("is_renderer_initiated", is_renderer_initiated_);
}

NavigateAndTriggerInstallDialogCommand::
    ~NavigateAndTriggerInstallDialogCommand() = default;

bool NavigateAndTriggerInstallDialogCommand::IsWebContentsDestroyed() {
  return !web_contents_ || web_contents_->IsBeingDestroyed();
}

void NavigateAndTriggerInstallDialogCommand::StartWithLock(
    std::unique_ptr<NoopLock> lock) {
  noop_lock_ = std::move(lock);

  content::NavigationController::LoadURLParams load_url_params(install_url_);

  // We specify a different page transition here because the common
  // `ui::PAGE_TRANSITION_LINK` can be intercepted by URL capturing logic.
  load_url_params.transition_type = ui::PAGE_TRANSITION_FROM_API;
  load_url_params.is_renderer_initiated = is_renderer_initiated_;
  // The `Navigate` implementation requires renderer-initiated navigations to
  // specify an initiator origin.
  load_url_params.initiator_origin = url::Origin::Create(origin_url_);

  content::WebContents* new_tab = ui_manager_->CreateNewTab();
  if (!new_tab) {
    // Browser may be shutting down.
    // TODO(b/331691742): Avoid starting commands when the browser is shutting
    // down.
    CompleteAndSelfDestruct(CommandResult::kFailure,
                            NavigateAndTriggerInstallDialogResult::kFailure);
    return;
  }
  web_contents_ = new_tab->GetWeakPtr();
  url_loader_->LoadUrl(
      std::move(load_url_params), web_contents_.get(),
      webapps::WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef,
      base::BindOnce(&NavigateAndTriggerInstallDialogCommand::OnUrlLoaded,
                     weak_factory_.GetWeakPtr()));
}

void NavigateAndTriggerInstallDialogCommand::OnUrlLoaded(
    webapps::WebAppUrlLoaderResult result) {
  GetMutableDebugValue().Set("WebAppUrlLoader::Result", base::ToString(result));
  if (IsWebContentsDestroyed()) {
    GetMutableDebugValue().Set("web_contents_destroyed", true);
    CompleteAndSelfDestruct(CommandResult::kSuccess,
                            NavigateAndTriggerInstallDialogResult::kFailure);
    return;
  }
  if (result != webapps::WebAppUrlLoaderResult::kUrlLoaded) {
    CompleteAndSelfDestruct(CommandResult::kFailure,
                            NavigateAndTriggerInstallDialogResult::kFailure);
    return;
  }
  data_retriever_->CheckInstallabilityAndRetrieveManifest(
      web_contents_.get(),
      base::BindOnce(
          &NavigateAndTriggerInstallDialogCommand::OnInstallabilityChecked,
          weak_factory_.GetWeakPtr()));
}

void NavigateAndTriggerInstallDialogCommand::OnInstallabilityChecked(
    blink::mojom::ManifestPtr opt_manifest,
    bool valid_manifest_for_web_app,
    webapps::InstallableStatusCode error_code) {
  GetMutableDebugValue().Set("webapps::InstallableStatusCode",
                             GetErrorMessage(error_code));
  if (IsWebContentsDestroyed()) {
    GetMutableDebugValue().Set("web_contents_destroyed", true);
    CompleteAndSelfDestruct(CommandResult::kSuccess,
                            NavigateAndTriggerInstallDialogResult::kFailure);
    return;
  }
  if (error_code != webapps::InstallableStatusCode::NO_ERROR_DETECTED) {
    CompleteAndSelfDestruct(CommandResult::kFailure,
                            NavigateAndTriggerInstallDialogResult::kFailure);
    return;
  }
  CHECK(opt_manifest);
  app_id_ = GenerateAppIdFromManifest(*opt_manifest);
  app_lock_ = std::make_unique<AppLock>();
  command_manager()->lock_manager().UpgradeAndAcquireLock(
      std::move(noop_lock_), *app_lock_, {app_id_},
      base::BindOnce(&NavigateAndTriggerInstallDialogCommand::OnAppLockGranted,
                     weak_factory_.GetWeakPtr()));
}

void NavigateAndTriggerInstallDialogCommand::OnAppLockGranted() {
  CHECK(app_lock_);
  CHECK(app_lock_->IsGranted());

  if (IsWebContentsDestroyed()) {
    GetMutableDebugValue().Set("web_contents_destroyed", true);
    CompleteAndSelfDestruct(CommandResult::kSuccess,
                            NavigateAndTriggerInstallDialogResult::kFailure);
    return;
  }
  CHECK(!app_id_.empty());

  std::optional<proto::InstallState> install_state =
      app_lock_->registrar().GetInstallState(app_id_);

  bool is_installable;
  if (!install_state) {
    is_installable = true;
  } else {
    switch (*install_state) {
      case web_app::proto::SUGGESTED_FROM_ANOTHER_DEVICE:
      case web_app::proto::SUGGESTED_FROM_MIGRATION:
        is_installable = true;
        break;
      case web_app::proto::INSTALLED_WITH_OS_INTEGRATION:
      case web_app::proto::INSTALLED_WITHOUT_OS_INTEGRATION:
        is_installable = false;
        break;
    }
  }

  if (!is_installable) {
    // If the app is already installed, we don't show the dialog. Since nothing
    // went wrong, this is still considered a success.
    CompleteAndSelfDestruct(
        CommandResult::kSuccess,
        NavigateAndTriggerInstallDialogResult::kAlreadyInstalled);
    return;
  }
  ui_manager_->TriggerInstallDialog(web_contents_.get(), source_,
                                    base::DoNothing());
  CompleteAndSelfDestruct(CommandResult::kSuccess,
                          NavigateAndTriggerInstallDialogResult::kDialogShown);
}

}  // namespace web_app
