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

#include "chrome/browser/extensions/api/dashboard_private/dashboard_private_api.h"

#include <utility>

#include "base/bind.h"
#include "base/thread_task_runner_handle.h"
#include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
#include "chrome/browser/profiles/profile.h"
#include "components/crx_file/id_util.h"
#include "extensions/common/extension.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request.h"
#include "url/gurl.h"

namespace extensions {

namespace ShowPermissionPromptForDelegatedInstall =
    api::dashboard_private::ShowPermissionPromptForDelegatedInstall;
namespace ShowPermissionPromptForDelegatedBundleInstall =
    api::dashboard_private::ShowPermissionPromptForDelegatedBundleInstall;

namespace {

// Error messages that can be returned by the API.
const char kAlreadyInstalledError[] = "This item is already installed";
const char kInvalidBundleError[] = "Invalid bundle";
const char kInvalidIconUrlError[] = "Invalid icon url";
const char kInvalidIdError[] = "Invalid id";
const char kInvalidManifestError[] = "Invalid manifest";
const char kUserCancelledError[] = "User cancelled install";

api::dashboard_private::Result WebstoreInstallHelperResultToApiResult(
    WebstoreInstallHelper::Delegate::InstallHelperResultCode result) {
  switch (result) {
    case WebstoreInstallHelper::Delegate::UNKNOWN_ERROR:
      return api::dashboard_private::RESULT_UNKNOWN_ERROR;
    case WebstoreInstallHelper::Delegate::ICON_ERROR:
      return api::dashboard_private::RESULT_ICON_ERROR;
    case WebstoreInstallHelper::Delegate::MANIFEST_ERROR:
      return api::dashboard_private::RESULT_MANIFEST_ERROR;
  }
  NOTREACHED();
  return api::dashboard_private::RESULT_NONE;
}

}  // namespace

DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::
    DashboardPrivateShowPermissionPromptForDelegatedInstallFunction() {
}

DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::
    ~DashboardPrivateShowPermissionPromptForDelegatedInstallFunction() {
}

ExtensionFunction::ResponseAction
DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::Run() {
  params_ = Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_);

  if (!crx_file::id_util::IdIsValid(params_->details.id)) {
    return RespondNow(BuildResponse(api::dashboard_private::RESULT_INVALID_ID,
                                    kInvalidIdError));
  }

  GURL icon_url;
  if (params_->details.icon_url) {
    icon_url = source_url().Resolve(*params_->details.icon_url);
    if (!icon_url.is_valid()) {
      return RespondNow(BuildResponse(
          api::dashboard_private::RESULT_INVALID_ICON_URL,
          kInvalidIconUrlError));
    }
  }

  net::URLRequestContextGetter* context_getter = nullptr;
  if (!icon_url.is_empty())
    context_getter = browser_context()->GetRequestContext();

  scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
      this, params_->details.id, params_->details.manifest, icon_url,
      context_getter);

  // The helper will call us back via OnWebstoreParseSuccess or
  // OnWebstoreParseFailure.
  helper->Start();

  // Matched with a Release in OnWebstoreParseSuccess/OnWebstoreParseFailure.
  AddRef();

  // The response is sent asynchronously in OnWebstoreParseSuccess/
  // OnWebstoreParseFailure.
  return RespondLater();
}

void DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::
    OnWebstoreParseSuccess(
    const std::string& id,
    const SkBitmap& icon,
    base::DictionaryValue* parsed_manifest) {
  CHECK_EQ(params_->details.id, id);
  CHECK(parsed_manifest);

  std::string localized_name = params_->details.localized_name ?
      *params_->details.localized_name : std::string();

  std::string error;
  dummy_extension_ = ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
      parsed_manifest,
      Extension::FROM_WEBSTORE,
      id,
      localized_name,
      std::string(),
      &error);

  if (!dummy_extension_.get()) {
    OnWebstoreParseFailure(params_->details.id,
                           WebstoreInstallHelper::Delegate::MANIFEST_ERROR,
                           kInvalidManifestError);
    return;
  }

  content::WebContents* web_contents = GetAssociatedWebContents();
  if (!web_contents) {
    // The browser window has gone away.
    Respond(BuildResponse(api::dashboard_private::RESULT_USER_CANCELLED,
                          kUserCancelledError));
    // Matches the AddRef in Run().
    Release();
    return;
  }
  scoped_ptr<ExtensionInstallPrompt::Prompt> prompt(
      new ExtensionInstallPrompt::Prompt(
          ExtensionInstallPrompt::DELEGATED_PERMISSIONS_PROMPT));
  prompt->set_delegated_username(details().delegated_user);

  install_prompt_.reset(new ExtensionInstallPrompt(web_contents));
  install_prompt_->ShowDialog(
      base::Bind(
          &DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::
              OnInstallPromptDone,
          this),
      dummy_extension_.get(), &icon, std::move(prompt),
      ExtensionInstallPrompt::GetDefaultShowDialogCallback());
  // Control flow finishes up in OnInstallPromptDone().
}

void DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::
    OnWebstoreParseFailure(
    const std::string& id,
    WebstoreInstallHelper::Delegate::InstallHelperResultCode result,
    const std::string& error_message) {
  CHECK_EQ(params_->details.id, id);

  Respond(BuildResponse(WebstoreInstallHelperResultToApiResult(result),
                        error_message));

  // Matches the AddRef in Run().
  Release();
}

void DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::
    OnInstallPromptDone(ExtensionInstallPrompt::Result result) {
  if (result == ExtensionInstallPrompt::Result::ACCEPTED) {
    Respond(
        BuildResponse(api::dashboard_private::RESULT_SUCCESS, std::string()));
  } else {
    Respond(BuildResponse(api::dashboard_private::RESULT_USER_CANCELLED,
                          kUserCancelledError));
  }

  Release();  // Matches the AddRef in Run().
}

ExtensionFunction::ResponseValue
DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::BuildResponse(
    api::dashboard_private::Result result, const std::string& error) {
  if (result != api::dashboard_private::RESULT_SUCCESS)
    return ErrorWithArguments(CreateResults(result), error);

  // The web store expects an empty string on success, so don't use
  // RESULT_SUCCESS here.
  return ArgumentList(
      CreateResults(api::dashboard_private::RESULT_EMPTY_STRING));
}

scoped_ptr<base::ListValue>
DashboardPrivateShowPermissionPromptForDelegatedInstallFunction::CreateResults(
    api::dashboard_private::Result result) const {
  return ShowPermissionPromptForDelegatedInstall::Results::Create(result);
}

DashboardPrivateShowPermissionPromptForDelegatedBundleInstallFunction::
    DashboardPrivateShowPermissionPromptForDelegatedBundleInstallFunction()
    : chrome_details_(this) {
}

DashboardPrivateShowPermissionPromptForDelegatedBundleInstallFunction::
    ~DashboardPrivateShowPermissionPromptForDelegatedBundleInstallFunction() {
}

ExtensionFunction::ResponseAction
DashboardPrivateShowPermissionPromptForDelegatedBundleInstallFunction::Run() {
  params_ = Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_);

  if (params_->contents.empty())
    return RespondNow(Error(kInvalidBundleError));

  if (params_->details.icon_url) {
    GURL icon_url = source_url().Resolve(*params_->details.icon_url);
    if (!icon_url.is_valid())
      return RespondNow(Error(kInvalidIconUrlError));

    // The bitmap fetcher will call us back via OnFetchComplete.
    icon_fetcher_.reset(new chrome::BitmapFetcher(icon_url, this));
    icon_fetcher_->Init(
        browser_context()->GetRequestContext(), std::string(),
        net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
        net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES);
    icon_fetcher_->Start();
  } else {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::Bind(
        &DashboardPrivateShowPermissionPromptForDelegatedBundleInstallFunction::
            OnFetchComplete,
        this, GURL(), nullptr));
  }

  AddRef();  // Balanced in OnFetchComplete.

  // The response is sent in OnFetchComplete or OnInstallApproval.
  return RespondLater();
}

void DashboardPrivateShowPermissionPromptForDelegatedBundleInstallFunction::
    OnFetchComplete(const GURL& url, const SkBitmap* bitmap) {
  BundleInstaller::ItemList items;
  for (const auto& entry : params_->contents) {
    BundleInstaller::Item item;
    item.id = entry.id;
    item.manifest = entry.manifest;
    item.localized_name = entry.localized_name;
    if (entry.icon_url)
      item.icon_url = source_url().Resolve(*entry.icon_url);
    items.push_back(item);
  }
  if (items.empty()) {
    Respond(Error(kAlreadyInstalledError));
    Release();  // Matches the AddRef in Run.
    return;
  }

  bundle_.reset(new BundleInstaller(chrome_details_.GetCurrentBrowser(),
                                    params_->details.localized_name,
                                    bitmap ? *bitmap : SkBitmap(),
                                    std::string(), details().delegated_user,
                                    items));

  bundle_->PromptForApproval(base::Bind(
      &DashboardPrivateShowPermissionPromptForDelegatedBundleInstallFunction::
          OnInstallApproval,
      this));

  Release();  // Matches the AddRef in Run.
}

void DashboardPrivateShowPermissionPromptForDelegatedBundleInstallFunction::
    OnInstallApproval(BundleInstaller::ApprovalState state) {
  if (state != BundleInstaller::APPROVED) {
    Respond(Error(state == BundleInstaller::USER_CANCELED
                      ? kUserCancelledError
                      : kInvalidBundleError));
    return;
  }

  Respond(NoArguments());
}

}  // namespace extensions