// 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 <utility>

#include "chromeos/ash/experiences/arc/overlay/arc_overlay_manager.h"
#include "chromeos/ash/experiences/arc/pay/arc_payment_app_bridge.h"
#include "components/payments/content/android_app_communication.h"
#include "components/payments/content/android_app_communication_helpers.h"
#include "components/payments/core/android_app_description.h"
#include "components/payments/core/native_error_strings.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"

namespace payments {
namespace {

// Invokes the TWA Android app in Android subsystem on Chrome OS.
class AndroidAppCommunicationAsh : public AndroidAppCommunication {
 public:
  explicit AndroidAppCommunicationAsh(content::BrowserContext* context)
      : AndroidAppCommunication(context),
        get_app_service_(base::BindRepeating(
            &arc::ArcPaymentAppBridge::GetForBrowserContext)) {}

  ~AndroidAppCommunicationAsh() override = default;

  // Disallow copy and assign.
  AndroidAppCommunicationAsh(const AndroidAppCommunicationAsh& other) = delete;
  AndroidAppCommunicationAsh& operator=(
      const AndroidAppCommunicationAsh& other) = delete;

  // AndroidAppCommunication implementation:
  void GetAppDescriptions(const std::string& twa_package_name,
                          GetAppDescriptionsCallback callback) override {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    if (twa_package_name.empty()) {
      // Chrome OS supports Android app payment only through a TWA. An empty
      // |twa_package_name| indicates that Chrome was not launched from a TWA,
      // so there're no payment apps available.
      std::move(callback).Run(/*error_message=*/std::nullopt,
                              /*app_descriptions=*/{});
      return;
    }

    if (!package_name_for_testing_.empty()) {
      std::move(callback).Run(
          /*error_message=*/std::nullopt,
          CreateAppForTesting(package_name_for_testing_, method_for_testing_));
      return;
    }

    auto* payment_app_service = get_app_service_.Run(context());
    if (!payment_app_service) {
      std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
                              /*app_descriptions=*/{});
      return;
    }

    payment_app_service->IsPaymentImplemented(
        twa_package_name, base::BindOnce(&OnIsImplemented, twa_package_name,
                                         std::move(callback)));
  }

  // AndroidAppCommunication implementation.
  void IsReadyToPay(const std::string& package_name,
                    const std::string& service_name,
                    const std::map<std::string, std::set<std::string>>&
                        stringified_method_data,
                    const GURL& top_level_origin,
                    const GURL& payment_request_origin,
                    const std::string& payment_request_id,
                    IsReadyToPayCallback callback) override {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    auto* payment_app_service = get_app_service_.Run(context());
    if (!payment_app_service) {
      std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
                              /*is_ready_to_pay=*/false);
      return;
    }

    std::optional<std::string> error_message;
    auto parameters = CreatePaymentParameters(
        package_name, service_name, stringified_method_data, top_level_origin,
        payment_request_origin, payment_request_id, &error_message);
    if (!parameters) {
      std::move(callback).Run(error_message, /*is_ready_to_pay=*/false);
      return;
    }

    payment_app_service->IsReadyToPay(
        std::move(parameters),
        base::BindOnce(&OnIsReadyToPay, std::move(callback)));
  }

  // AndroidAppCommunication implementation.
  void InvokePaymentApp(
      const std::string& package_name,
      const std::string& activity_name,
      const std::map<std::string, std::set<std::string>>&
          stringified_method_data,
      const GURL& top_level_origin,
      const GURL& payment_request_origin,
      const std::string& payment_request_id,
      const base::UnguessableToken& request_token,
      content::WebContents* web_contents,
      InvokePaymentAppCallback callback) override {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    // Create and register a token with ArcOverlayManager for the
    // browser window. Doing so is required to allow the Android Play Billing
    // interface to be overlaid on top of the browser window.
    ash::ArcOverlayManager* const overlay_manager =
        ash::ArcOverlayManager::instance();
    base::ScopedClosureRunner overlay_state =
        overlay_manager->RegisterHostWindow(request_token.ToString(),
                                            web_contents->GetNativeView());

    std::optional<std::string> error_message;
    if (package_name_for_testing_ == package_name) {
      std::move(callback).Run(error_message,
                              /*is_activity_result_ok=*/true,
                              method_for_testing_, response_for_testing_);
      return;
    }

    auto* payment_app_service = get_app_service_.Run(context());
    if (!payment_app_service) {
      std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
                              /*is_activity_result_ok=*/false,
                              /*payment_method_identifier=*/"",
                              /*stringified_details=*/kEmptyDictionaryJson);
      return;
    }

    auto parameters = CreatePaymentParameters(
        package_name, activity_name, stringified_method_data, top_level_origin,
        payment_request_origin, payment_request_id, &error_message);
    if (!parameters) {
      std::move(callback).Run(error_message,
                              /*is_activity_result_ok=*/false,
                              /*payment_method_identifier=*/"",
                              /*stringified_details=*/kEmptyDictionaryJson);
      return;
    }
    parameters->request_token = request_token.ToString();

    payment_app_service->InvokePaymentApp(
        std::move(parameters),
        base::BindOnce(&OnPaymentAppResponse, std::move(callback),
                       std::move(overlay_state)));
  }

  void AbortPaymentApp(const base::UnguessableToken& token,
                       AbortPaymentAppCallback callback) override {
    auto* payment_app_service = get_app_service_.Run(context());
    if (!payment_app_service) {
      std::move(callback).Run(false);
      return;
    }

    payment_app_service->AbortPaymentApp(token.ToString(), std::move(callback));
  }

  // AndroidAppCommunication implementation.
  void SetForTesting() override {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    get_app_service_ = base::BindRepeating(
        &arc::ArcPaymentAppBridge::GetForBrowserContextForTesting);
  }

  // AndroidAppCommunication implementation.
  void SetAppForTesting(const std::string& package_name,
                        const std::string& method,
                        const std::string& response) override {
    package_name_for_testing_ = package_name;
    method_for_testing_ = method;
    response_for_testing_ = response;
  }

 private:
  std::string package_name_for_testing_;
  std::string method_for_testing_;
  std::string response_for_testing_;

  base::RepeatingCallback<arc::ArcPaymentAppBridge*(content::BrowserContext*)>
      get_app_service_;
};

}  // namespace

// Declared in cross-platform header file. See:
// //components/payments/content/android_app_communication.h
// static
std::unique_ptr<AndroidAppCommunication> AndroidAppCommunication::Create(
    content::BrowserContext* context) {
  return std::make_unique<AndroidAppCommunicationAsh>(context);
}

}  // namespace payments
