// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef EXTENSIONS_BROWSER_WEBSTORE_INSTALLER_H_
#define EXTENSIONS_BROWSER_WEBSTORE_INSTALLER_H_

#include <list>
#include <memory>
#include <string>

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/timer/timer.h"
#include "base/version.h"
#include "components/download/public/common/download_interrupt_reasons.h"
#include "components/download/public/common/download_item.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/browser/install/crx_install_error.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/manifest_handlers/shared_module_info.h"
#include "url/gurl.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace base {
class FilePath;
}

namespace content {
class BrowserContext;
class WebContents;
}  // namespace content

namespace extensions {

class CrxInstaller;
class Extension;
class ExtensionRegistry;
struct InstallApproval;

// Downloads and installs extensions from the web store.
class WebstoreInstaller : public ExtensionRegistryObserver,
                          public download::DownloadItem::Observer,
                          public base::RefCountedThreadSafe<
                              WebstoreInstaller,
                              content::BrowserThread::DeleteOnUIThread> {
 public:
  enum InstallSource {
    // Inline installs trigger slightly different behavior (install source
    // is different, download referrers are the item's page in the gallery).
    // TODO(ackermanb): Remove once server side metrics (omaha) tracking with
    // this enum is figured out with any of the subclasses of
    // WebstoreStandaloneInstaller.
    INSTALL_SOURCE_INLINE,
    INSTALL_SOURCE_APP_LAUNCHER,
    INSTALL_SOURCE_OTHER
  };

  enum FailureReason {
    FAILURE_REASON_CANCELLED,
    FAILURE_REASON_DEPENDENCY_NOT_FOUND,
    FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE,
    FAILURE_REASON_OTHER
  };

  using SuccessCallback = base::OnceCallback<void(const std::string&)>;
  using FailureCallback = base::OnceCallback<
      void(const std::string&, const std::string&, FailureReason)>;

  // Gets the InstallApproval associated with the `download`, or nullptr if
  // there's none. Note that the InstallApproval is owned by `download`.
  static const InstallApproval* GetAssociatedApproval(
      const download::DownloadItem& download);

  // Creates a WebstoreInstaller for downloading and installing the extension
  // with the given `id` from the Chrome Web Store. The `success_callback` and
  // `failure_callback` parameters must not be null. This also associates the
  // `approval` with this install.
  WebstoreInstaller(content::BrowserContext* browser_context,
                    SuccessCallback success_callback,
                    FailureCallback failure_callback,
                    content::WebContents* web_contents,
                    const std::string& id,
                    std::unique_ptr<InstallApproval> approval,
                    InstallSource source);

  // Starts downloading and installing the extension.
  void Start();

  // ExtensionRegistryObserver.
  void OnExtensionInstalled(content::BrowserContext* browser_context,
                            const Extension* extension,
                            bool is_update) override;

  // Instead of using the default download directory, use `directory` instead.
  // This does *not* transfer ownership of `directory`.
  static void SetDownloadDirectoryForTests(base::FilePath* directory);

 protected:
  // For testing.
  ~WebstoreInstaller() override;

 private:
  FRIEND_TEST_ALL_PREFIXES(WebstoreInstallerTest, PlatformParams);
  FRIEND_TEST_ALL_PREFIXES(WebstoreInstallerTest, AuthUserParameterEncoding);
  friend struct content::BrowserThread::DeleteOnThread<
      content::BrowserThread::UI>;
  friend class base::DeleteHelper<WebstoreInstaller>;

  // Helper to get install URL.
  static GURL GetWebstoreInstallURL(const std::string& extension_id,
                                    InstallSource source);

  // Appends or replaces the authuser query parameter in the URL.
  // Exposed for testing.
  static void MaybeAppendAuthUserParameter(const std::string& authuser,
                                           GURL& url);

  // DownloadManager::DownloadUrl callback.
  void OnDownloadStarted(const std::string& extension_id,
                         download::DownloadItem* item,
                         download::DownloadInterruptReason interrupt_reason);

  // DownloadItem::Observer implementation:
  void OnDownloadUpdated(download::DownloadItem* download) override;
  void OnDownloadDestroyed(download::DownloadItem* download) override;

  // Downloads next pending module in `pending_modules_`.
  void DownloadNextPendingModule();

  // Downloads and installs a single Crx with the given `extension_id`.
  // This function is used for both the extension Crx and dependences.
  void DownloadCrx(const std::string& extension_id, InstallSource source);

  // Starts downloading the extension with ID `extension_id` to `file_path`.
  void StartDownload(const std::string& extension_id,
                     const base::FilePath& file_path);

  // Updates the InstallTracker with the latest download progress.
  void UpdateDownloadProgress();

  // Creates and starts CrxInstaller for the downloaded extension package.
  void StartCrxInstaller(const download::DownloadItem& item);

  // Reports an install `error` to the delegate for the given extension if this
  // managed its installation. This also removes the associated PendingInstall.
  void ReportFailure(const std::string& error, FailureReason reason);

  // Reports a successful install to the delegate for the given extension if
  // this managed its installation. This also removes the associated
  // PendingInstall.
  void ReportSuccess();

  // Called when crx_installer_->InstallCrx() finishes.
  void OnInstallerDone(const std::optional<CrxInstallError>& error);

  base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
      extension_registry_observation_{this};
  base::WeakPtr<content::WebContents> web_contents_;
  raw_ptr<content::BrowserContext> browser_context_;
  SuccessCallback success_callback_;
  FailureCallback failure_callback_;
  std::string id_;
  InstallSource install_source_;
  // The DownloadItem is owned by the DownloadManager and is valid from when
  // OnDownloadStarted is called (with no error) until OnDownloadDestroyed().
  raw_ptr<download::DownloadItem, DanglingUntriaged> download_item_ = nullptr;
  // Used to periodically update the extension's download status. This will
  // trigger at least every second, though sometimes more frequently (depending
  // on number of modules, etc).
  base::OneShotTimer download_progress_timer_;
  std::unique_ptr<InstallApproval> approval_;
  GURL download_url_;
  scoped_refptr<CrxInstaller> crx_installer_;

  // Pending modules.
  std::list<SharedModuleInfo::ImportInfo> pending_modules_;
  // Total extension modules we need download and install (the main module and
  // depedences).
  int total_modules_ = 0;
  bool download_started_ = false;

  base::WeakPtrFactory<WebstoreInstaller> weak_ptr_factory_{this};
};

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_WEBSTORE_INSTALLER_H_
