// 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/jobs/install_placeholder_job.h"

#include <memory>

#include "base/containers/flat_set.h"
#include "base/functional/callback_helpers.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/test_future.h"
#include "chrome/browser/web_applications/commands/web_app_command.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/locks/shared_web_contents_with_app_lock.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
#include "chrome/browser/web_applications/test/fake_os_integration_manager.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/fake_web_contents_manager.h"
#include "chrome/browser/web_applications/test/mock_data_retriever.h"
#include "chrome/browser/web_applications/test/test_web_app_url_loader.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_management_type.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/web_contents/web_app_url_loader.h"
#include "components/webapps/common/web_app_id.h"
#include "net/http/http_status_code.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace web_app {
namespace {

class InstallPlaceholderJobWrapperCommand
    : public WebAppCommand<SharedWebContentsWithAppLock,
                           webapps::InstallResultCode,
                           webapps::AppId> {
 public:
  InstallPlaceholderJobWrapperCommand(
      Profile* profile,
      const ExternalInstallOptions& install_options,
      InstallPlaceholderJob::InstallAndReplaceCallback callback,
      std::unique_ptr<WebAppDataRetriever> data_retriever = nullptr,
      std::unique_ptr<webapps::WebAppUrlLoader> url_loader = nullptr)
      : WebAppCommand<SharedWebContentsWithAppLock,
                      webapps::InstallResultCode,
                      webapps::AppId>(
            "InstallPlaceholderJobWrapperCommand",
            SharedWebContentsWithAppLockDescription(
                base::flat_set<webapps::AppId>{
                    GenerateAppId(/*manifest_id_path=*/std::nullopt,
                                  install_options.install_url)}),
            std::move(callback),
            /*args_for_shutdown=*/
            std::make_tuple(webapps::InstallResultCode::
                                kCancelledOnWebAppProviderShuttingDown,
                            webapps::AppId())),
        profile_(*profile),
        install_options_(install_options),
        data_retriever_(std::move(data_retriever)),
        url_loader_(std::move(url_loader)) {}

  ~InstallPlaceholderJobWrapperCommand() override = default;

  void StartWithLock(
      std::unique_ptr<SharedWebContentsWithAppLock> lock) override {
    lock_ = std::move(lock);
    install_placeholder_job_ = std::make_unique<InstallPlaceholderJob>(
        &*profile_, *GetMutableDebugValue().EnsureDict("job"), install_options_,
        base::BindOnce(
            &InstallPlaceholderJobWrapperCommand::OnPlaceholderInstalled,
            weak_factory_.GetWeakPtr()),
        *lock_);
    if (url_loader_) {
      install_placeholder_job_->SetUrlLoaderForTesting(std::move(url_loader_));
    }

    if (data_retriever_) {
      install_placeholder_job_->SetDataRetrieverForTesting(
          std::move(data_retriever_));
    }
    install_placeholder_job_->Start();
  }

  void OnPlaceholderInstalled(webapps::InstallResultCode code,
                              webapps::AppId app_id) {
    CompleteAndSelfDestruct(webapps::IsSuccess(code) ? CommandResult::kSuccess
                                                     : CommandResult::kFailure,
                            code, app_id);
  }

 private:
  raw_ref<Profile> profile_;
  ExternalInstallOptions install_options_;
  std::unique_ptr<WebAppDataRetriever> data_retriever_;
  std::unique_ptr<webapps::WebAppUrlLoader> url_loader_;

  std::unique_ptr<SharedWebContentsWithAppLock> lock_;

  std::unique_ptr<InstallPlaceholderJob> install_placeholder_job_;

  base::WeakPtrFactory<InstallPlaceholderJobWrapperCommand> weak_factory_{this};
};

class InstallPlaceholderJobTest : public WebAppTest {
 public:
  InstallPlaceholderJobTest()
      : WebAppTest(WebAppTest::WithTestUrlLoaderFactory()) {}
  const GURL kInstallUrl = GURL("https://example.com");

  void SetUp() override {
    WebAppTest::SetUp();
    test::AwaitStartWebAppProviderAndSubsystems(profile());
  }

  void TearDown() override {
    WebAppTest::TearDown();
  }

  WebAppProvider* provider() { return WebAppProvider::GetForTest(profile()); }

  FakeOsIntegrationManager& fake_os_integration_manager() {
    return static_cast<FakeOsIntegrationManager&>(
        provider()->os_integration_manager());
  }
};

TEST_F(InstallPlaceholderJobTest, InstallPlaceholder) {
  ExternalInstallOptions options(kInstallUrl, mojom::UserDisplayMode::kBrowser,
                                 ExternalInstallSource::kExternalPolicy);
  base::test::TestFuture<webapps::InstallResultCode, webapps::AppId> future;

  provider()->command_manager().ScheduleCommand(
      std::make_unique<InstallPlaceholderJobWrapperCommand>(
          profile(), options, future.GetCallback()));

  EXPECT_EQ(future.Get<0>(), webapps::InstallResultCode::kSuccessNewInstall);
  const webapps::AppId app_id = future.Get<1>();
  EXPECT_TRUE(provider()->registrar_unsafe().IsPlaceholderApp(
      app_id, WebAppManagement::kPolicy));
  std::optional<proto::os_state::WebAppOsIntegration> os_state =
      provider()->registrar_unsafe().GetAppCurrentOsIntegrationState(app_id);
  ASSERT_TRUE(os_state.has_value());
  EXPECT_TRUE(os_state->has_shortcut());
  EXPECT_EQ(os_state->run_on_os_login().run_on_os_login_mode(),
            proto::os_state::RunOnOsLogin::MODE_NOT_RUN);
}

TEST_F(InstallPlaceholderJobTest, InstallPlaceholderWithOverrideIconUrl) {
  data_decoder::test::InProcessDataDecoder data_decoder;
  ExternalInstallOptions options(kInstallUrl, mojom::UserDisplayMode::kBrowser,
                                 ExternalInstallSource::kExternalPolicy);
  const GURL icon_url("https://somedifferentoriginexample.com/test.png");
  options.override_icon_url = icon_url;
  base::test::TestFuture<webapps::InstallResultCode, webapps::AppId> future;

  auto url_loader = std::make_unique<web_app::TestWebAppUrlLoader>();
  url_loader->SetNextLoadUrlResult(kInstallUrl,
                                   webapps::WebAppUrlLoaderResult::kUrlLoaded);

  std::string png_bytes =
      "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00"
      "\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53\xDE\x00\x00\x00"
      "\x0C\x49\x44\x41\x54\x78\x9C\x63\xF8\xCF\xC0\x00\x00\x03\x01\x01\x00\x18"
      "\xDD\x8D\xB0\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82";
  profile_url_loader_factory().AddResponse(icon_url.spec(), png_bytes);

  auto command = std::make_unique<InstallPlaceholderJobWrapperCommand>(
      profile(), options, future.GetCallback(), /*data_retriever=*/nullptr,
      std::move(url_loader));
  provider()->command_manager().ScheduleCommand(std::move(command));

  EXPECT_EQ(future.Get<0>(), webapps::InstallResultCode::kSuccessNewInstall);
  const webapps::AppId app_id = future.Get<1>();
  EXPECT_TRUE(provider()->registrar_unsafe().IsPlaceholderApp(
      app_id, WebAppManagement::kPolicy));
  std::optional<proto::os_state::WebAppOsIntegration> os_state =
      provider()->registrar_unsafe().GetAppCurrentOsIntegrationState(app_id);
  ASSERT_TRUE(os_state.has_value());
  EXPECT_TRUE(os_state->has_shortcut());
}

}  // namespace
}  // namespace web_app
