// 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 "components/autofill/core/browser/payments/payments_client.h"

#include <utility>

#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill/core/browser/payments/payments_request.h"
#include "components/autofill/core/common/autofill_switches.h"
#include "components/data_use_measurement/core/data_use_user_data.h"
#include "google_apis/gaia/identity_provider.h"
#include "net/base/escape.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context_getter.h"

namespace autofill {
namespace payments {

namespace {

const char kPaymentsRequestHost[] = "https://wallet.google.com";
const char kPaymentsRequestHostSandbox[] = "https://sandbox.google.com";

const char kUnmaskCardRequestPath[] =
    "payments/apis-secure/creditcardservice/getrealpan?s7e_suffix=chromewallet";
const char kUnmaskCardRequestFormat[] =
    "requestContentType=application/json; charset=utf-8&request=%s"
    "&s7e_13_cvc=%s";

const char kGetUploadDetailsRequestPath[] =
    "payments/apis/chromepaymentsservice/getdetailsforsavecard";

const char kUploadCardRequestPath[] =
    "payments/apis-secure/chromepaymentsservice/savecard"
    "?s7e_suffix=chromewallet";
const char kUploadCardRequestFormat[] =
    "requestContentType=application/json; charset=utf-8&request=%s"
    "&s7e_1_pan=%s&s7e_13_cvc=%s";

const char kTokenServiceConsumerId[] = "wallet_client";
const char kPaymentsOAuth2Scope[] =
    "https://www.googleapis.com/auth/wallet.chrome";

// This is mostly copied from wallet_service_url.cc, which is currently in
// content/, hence inaccessible from here.
bool IsPaymentsProductionEnabled() {
  // If the command line flag exists, it takes precedence.
  const base::CommandLine* command_line =
      base::CommandLine::ForCurrentProcess();
  std::string sandbox_enabled(
      command_line->GetSwitchValueASCII(switches::kWalletServiceUseSandbox));
  if (!sandbox_enabled.empty())
    return sandbox_enabled != "1";

#if defined(ENABLE_PROD_WALLET_SERVICE)
  return true;
#else
  return false;
#endif
}

GURL GetRequestUrl(const std::string& path) {
  if (base::CommandLine::ForCurrentProcess()->HasSwitch("sync-url")) {
    if (IsPaymentsProductionEnabled()) {
      LOG(ERROR) << "You are using production Payments but you specified a "
                    "--sync-url. You likely want to disable the sync sandbox "
                    "or switch to sandbox Payments. Both are controlled in "
                    "about:flags.";
    }
  } else if (!IsPaymentsProductionEnabled()) {
    LOG(ERROR) << "You are using sandbox Payments but you didn't specify a "
                  "--sync-url. You likely want to enable the sync sandbox "
                  "or switch to production Payments. Both are controlled in "
                  "about:flags.";
  }

  GURL base(IsPaymentsProductionEnabled() ? kPaymentsRequestHost
                                          : kPaymentsRequestHostSandbox);
  return base.Resolve(path);
}

scoped_ptr<base::DictionaryValue> BuildRiskDictionary(
    const std::string& encoded_risk_data) {
  scoped_ptr<base::DictionaryValue> risk_data(new base::DictionaryValue());
#if defined(OS_IOS)
  // Browser fingerprinting is not available on iOS. Instead, we generate
  // RiskAdvisoryData.
  risk_data->SetString("message_type", "RISK_ADVISORY_DATA");
  risk_data->SetString("encoding_type", "BASE_64_URL");
#else
  risk_data->SetString("message_type", "BROWSER_NATIVE_FINGERPRINTING");
  risk_data->SetString("encoding_type", "BASE_64");
#endif

  risk_data->SetString("value", encoded_risk_data);

  return risk_data;
}

scoped_ptr<base::DictionaryValue> BuildAddressDictionary(
    const AutofillProfile& profile,
    const std::string& app_locale) {
  scoped_ptr<base::DictionaryValue> address(new base::DictionaryValue());

  scoped_ptr<base::DictionaryValue> postal_address(new base::DictionaryValue());
  postal_address->SetString(
      "recipient_name", profile.GetInfo(AutofillType(NAME_FULL), app_locale));

  scoped_ptr<base::ListValue> address_lines(new base::ListValue());
  const base::string16 address_line1 =
      profile.GetInfo(AutofillType(ADDRESS_HOME_LINE1), app_locale);
  if (!address_line1.empty())
    address_lines->AppendString(address_line1);
  const base::string16 address_line2 =
      profile.GetInfo(AutofillType(ADDRESS_HOME_LINE2), app_locale);
  if (!address_line2.empty())
    address_lines->AppendString(address_line2);
  const base::string16 address_line3 =
      profile.GetInfo(AutofillType(ADDRESS_HOME_LINE3), app_locale);
  if (!address_line3.empty())
    address_lines->AppendString(address_line3);
  if (!address_lines->empty())
    postal_address->Set("address_line", std::move(address_lines));

  const base::string16 city =
      profile.GetInfo(AutofillType(ADDRESS_HOME_CITY), app_locale);
  if (!city.empty())
    postal_address->SetString("locality_name", city);

  const base::string16 state =
      profile.GetInfo(AutofillType(ADDRESS_HOME_STATE), app_locale);
  if (!state.empty())
    postal_address->SetString("administrative_area_name", state);

  postal_address->SetString(
      "postal_code_number",
      profile.GetInfo(AutofillType(ADDRESS_HOME_ZIP), app_locale));

  // Use GetRawInfo to get a country code instead of the country name:
  const base::string16 country_code = profile.GetRawInfo(ADDRESS_HOME_COUNTRY);
  if (!country_code.empty())
    postal_address->SetString("country_name_code", country_code);

  address->Set("postal_address", std::move(postal_address));

  const base::string16 phone_number =
      profile.GetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), app_locale);
  if (!phone_number.empty())
    address->SetString("phone_number", phone_number);

  return address;
}

class UnmaskCardRequest : public PaymentsRequest {
 public:
  UnmaskCardRequest(const PaymentsClient::UnmaskRequestDetails& request_details)
      : request_details_(request_details) {
    DCHECK_EQ(CreditCard::MASKED_SERVER_CARD,
              request_details.card.record_type());
  }
  ~UnmaskCardRequest() override {}

  std::string GetRequestUrlPath() override { return kUnmaskCardRequestPath; }

  std::string GetRequestContentType() override {
    return "application/x-www-form-urlencoded";
  }

  std::string GetRequestContent() override {
    base::DictionaryValue request_dict;
    request_dict.SetString("encrypted_cvc", "__param:s7e_13_cvc");
    request_dict.SetString("credit_card_id", request_details_.card.server_id());
    request_dict.Set("risk_data_encoded",
                     BuildRiskDictionary(request_details_.risk_data));
    request_dict.Set("context", make_scoped_ptr(new base::DictionaryValue()));

    int value = 0;
    if (base::StringToInt(request_details_.user_response.exp_month, &value))
      request_dict.SetInteger("expiration_month", value);
    if (base::StringToInt(request_details_.user_response.exp_year, &value))
      request_dict.SetInteger("expiration_year", value);

    std::string json_request;
    base::JSONWriter::Write(request_dict, &json_request);
    std::string request_content = base::StringPrintf(
        kUnmaskCardRequestFormat,
        net::EscapeUrlEncodedData(json_request, true).c_str(),
        net::EscapeUrlEncodedData(
            base::UTF16ToASCII(request_details_.user_response.cvc), true)
            .c_str());
    VLOG(3) << "getrealpan request body: " << request_content;
    return request_content;
  }

  void ParseResponse(scoped_ptr<base::DictionaryValue> response) override {
    response->GetString("pan", &real_pan_);
  }

  bool IsResponseComplete() override { return !real_pan_.empty(); }

  void RespondToDelegate(PaymentsClientDelegate* delegate,
                         AutofillClient::PaymentsRpcResult result) override {
    delegate->OnDidGetRealPan(result, real_pan_);
  }

 private:
  PaymentsClient::UnmaskRequestDetails request_details_;
  std::string real_pan_;
};

class GetUploadDetailsRequest : public PaymentsRequest {
 public:
  GetUploadDetailsRequest(const std::string& app_locale)
      : app_locale_(app_locale) {}
  ~GetUploadDetailsRequest() override {}

  std::string GetRequestUrlPath() override {
    return kGetUploadDetailsRequestPath;
  }

  std::string GetRequestContentType() override { return "application/json"; }

  std::string GetRequestContent() override {
    base::DictionaryValue request_dict;
    scoped_ptr<base::DictionaryValue> context(new base::DictionaryValue());
    context->SetString("language_code", app_locale_);
    request_dict.Set("context", std::move(context));

    std::string request_content;
    base::JSONWriter::Write(request_dict, &request_content);
    VLOG(3) << "getdetailsforsavecard request body: " << request_content;
    return request_content;
  }

  void ParseResponse(scoped_ptr<base::DictionaryValue> response) override {
    response->GetString("context_token", &context_token_);
    base::DictionaryValue* unowned_legal_message;
    if (response->GetDictionary("legal_message", &unowned_legal_message))
      legal_message_ = unowned_legal_message->CreateDeepCopy();
  }

  bool IsResponseComplete() override {
    return !context_token_.empty() && legal_message_;
  }

  void RespondToDelegate(PaymentsClientDelegate* delegate,
                         AutofillClient::PaymentsRpcResult result) override {
    delegate->OnDidGetUploadDetails(result, context_token_,
                                    std::move(legal_message_));
  }

 private:
  std::string app_locale_;
  base::string16 context_token_;
  scoped_ptr<base::DictionaryValue> legal_message_;
};

class UploadCardRequest : public PaymentsRequest {
 public:
  UploadCardRequest(const PaymentsClient::UploadRequestDetails& request_details)
      : request_details_(request_details) {}
  ~UploadCardRequest() override {}

  std::string GetRequestUrlPath() override { return kUploadCardRequestPath; }

  std::string GetRequestContentType() override {
    return "application/x-www-form-urlencoded";
  }

  std::string GetRequestContent() override {
    base::DictionaryValue request_dict;
    request_dict.SetString("encrypted_pan", "__param:s7e_1_pan");
    request_dict.SetString("encrypted_cvc", "__param:s7e_13_cvc");
    request_dict.Set("risk_data_encoded",
                     BuildRiskDictionary(request_details_.risk_data));

    const std::string& app_locale = request_details_.app_locale;
    scoped_ptr<base::DictionaryValue> context(new base::DictionaryValue());
    context->SetString("language_code", app_locale);
    request_dict.Set("context", std::move(context));

    request_dict.SetString(
        "cardholder_name",
        request_details_.card.GetInfo(AutofillType(CREDIT_CARD_NAME_FULL),
                                      app_locale));

    scoped_ptr<base::ListValue> addresses(new base::ListValue());
    for (const AutofillProfile& profile : request_details_.profiles) {
      addresses->Append(BuildAddressDictionary(profile, app_locale));
    }
    request_dict.Set("address", std::move(addresses));

    request_dict.SetString("context_token", request_details_.context_token);

    int value = 0;
    base::string16 exp_month = request_details_.card.GetInfo(
        AutofillType(CREDIT_CARD_EXP_MONTH), app_locale);
    base::string16 exp_year = request_details_.card.GetInfo(
        AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale);
    if (base::StringToInt(exp_month, &value))
      request_dict.SetInteger("expiration_month", value);
    if (base::StringToInt(exp_year, &value))
      request_dict.SetInteger("expiration_year", value);

    base::string16 pan = request_details_.card.GetInfo(
        AutofillType(CREDIT_CARD_NUMBER), app_locale);
    std::string json_request;
    base::JSONWriter::Write(request_dict, &json_request);
    std::string request_content = base::StringPrintf(
        kUploadCardRequestFormat,
        net::EscapeUrlEncodedData(json_request, true).c_str(),
        net::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str(),
        net::EscapeUrlEncodedData(base::UTF16ToASCII(request_details_.cvc),
                                  true)
            .c_str());
    VLOG(3) << "savecard request body: " << request_content;
    return request_content;
  }

  void ParseResponse(scoped_ptr<base::DictionaryValue> response) override {}

  bool IsResponseComplete() override { return true; }

  void RespondToDelegate(PaymentsClientDelegate* delegate,
                         AutofillClient::PaymentsRpcResult result) override {
    delegate->OnDidUploadCard(result);
  }

 private:
  PaymentsClient::UploadRequestDetails request_details_;
};

}  // namespace

PaymentsClient::UnmaskRequestDetails::UnmaskRequestDetails() {}
PaymentsClient::UnmaskRequestDetails::~UnmaskRequestDetails() {}

PaymentsClient::UploadRequestDetails::UploadRequestDetails() {}
PaymentsClient::UploadRequestDetails::UploadRequestDetails(
    const UploadRequestDetails& other) = default;
PaymentsClient::UploadRequestDetails::~UploadRequestDetails() {}

PaymentsClient::PaymentsClient(net::URLRequestContextGetter* context_getter,
                               PaymentsClientDelegate* delegate)
    : OAuth2TokenService::Consumer(kTokenServiceConsumerId),
      context_getter_(context_getter),
      delegate_(delegate),
      has_retried_authorization_(false),
      weak_ptr_factory_(this) {
  DCHECK(delegate);
}

PaymentsClient::~PaymentsClient() {}

void PaymentsClient::Prepare() {
  if (access_token_.empty())
    StartTokenFetch(false);
}

void PaymentsClient::UnmaskCard(
    const PaymentsClient::UnmaskRequestDetails& request_details) {
  IssueRequest(make_scoped_ptr(new UnmaskCardRequest(request_details)), true);
}

void PaymentsClient::GetUploadDetails(const std::string& app_locale) {
  IssueRequest(make_scoped_ptr(new GetUploadDetailsRequest(app_locale)), false);
}

void PaymentsClient::UploadCard(
    const PaymentsClient::UploadRequestDetails& request_details) {
  IssueRequest(make_scoped_ptr(new UploadCardRequest(request_details)), true);
}

void PaymentsClient::IssueRequest(scoped_ptr<PaymentsRequest> request,
                                  bool authenticate) {
  request_ = std::move(request);
  has_retried_authorization_ = false;
  InitializeUrlFetcher();

  if (!authenticate)
    url_fetcher_->Start();
  else if (access_token_.empty())
    StartTokenFetch(false);
  else
    SetOAuth2TokenAndStartRequest();
}

void PaymentsClient::InitializeUrlFetcher() {
  url_fetcher_ =
      net::URLFetcher::Create(0, GetRequestUrl(request_->GetRequestUrlPath()),
                              net::URLFetcher::POST, this);

  data_use_measurement::DataUseUserData::AttachToFetcher(
      url_fetcher_.get(), data_use_measurement::DataUseUserData::AUTOFILL);
  url_fetcher_->SetRequestContext(context_getter_.get());
  url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
                             net::LOAD_DO_NOT_SEND_COOKIES |
                             net::LOAD_DISABLE_CACHE);

  url_fetcher_->SetUploadData(request_->GetRequestContentType(),
                              request_->GetRequestContent());
}

void PaymentsClient::CancelRequest() {
  request_.reset();
  url_fetcher_.reset();
  access_token_request_.reset();
  access_token_.clear();
  has_retried_authorization_ = false;
}

void PaymentsClient::OnURLFetchComplete(const net::URLFetcher* source) {
  DCHECK_EQ(source, url_fetcher_.get());

  // |url_fetcher_|, which is aliased to |source|, might continue to be used in
  // this method, but should be freed once control leaves the method.
  scoped_ptr<net::URLFetcher> scoped_url_fetcher(std::move(url_fetcher_));
  scoped_ptr<base::DictionaryValue> response_dict;
  int response_code = source->GetResponseCode();
  std::string data;
  source->GetResponseAsString(&data);
  VLOG(2) << "Got data: " << data;

  AutofillClient::PaymentsRpcResult result = AutofillClient::SUCCESS;

  switch (response_code) {
    // Valid response.
    case net::HTTP_OK: {
      std::string error_code;
      scoped_ptr<base::Value> message_value = base::JSONReader::Read(data);
      if (message_value.get() &&
          message_value->IsType(base::Value::TYPE_DICTIONARY)) {
        response_dict.reset(
            static_cast<base::DictionaryValue*>(message_value.release()));
        response_dict->GetString("error.code", &error_code);
        request_->ParseResponse(std::move(response_dict));
      }

      if (base::LowerCaseEqualsASCII(error_code, "internal"))
        result = AutofillClient::TRY_AGAIN_FAILURE;
      else if (!error_code.empty() || !request_->IsResponseComplete())
        result = AutofillClient::PERMANENT_FAILURE;

      break;
    }

    case net::HTTP_UNAUTHORIZED: {
      if (has_retried_authorization_) {
        result = AutofillClient::PERMANENT_FAILURE;
        break;
      }
      has_retried_authorization_ = true;

      InitializeUrlFetcher();
      StartTokenFetch(true);
      return;
    }

    // TODO(estade): is this actually how network connectivity issues are
    // reported?
    case net::HTTP_REQUEST_TIMEOUT: {
      result = AutofillClient::NETWORK_ERROR;
      break;
    }

    // Handle anything else as a generic (permanent) failure.
    default: {
      result = AutofillClient::PERMANENT_FAILURE;
      break;
    }
  }

  if (result != AutofillClient::SUCCESS) {
    VLOG(1) << "Payments returned error: " << response_code
            << " with data: " << data;
  }

  request_->RespondToDelegate(delegate_, result);
}

void PaymentsClient::OnGetTokenSuccess(
    const OAuth2TokenService::Request* request,
    const std::string& access_token,
    const base::Time& expiration_time) {
  DCHECK_EQ(request, access_token_request_.get());
  access_token_ = access_token;
  if (url_fetcher_)
    SetOAuth2TokenAndStartRequest();

  access_token_request_.reset();
}

void PaymentsClient::OnGetTokenFailure(
    const OAuth2TokenService::Request* request,
    const GoogleServiceAuthError& error) {
  DCHECK_EQ(request, access_token_request_.get());
  VLOG(1) << "Unhandled OAuth2 error: " << error.ToString();
  if (url_fetcher_) {
    url_fetcher_.reset();
    request_->RespondToDelegate(delegate_, AutofillClient::PERMANENT_FAILURE);
  }
  access_token_request_.reset();
}

void PaymentsClient::StartTokenFetch(bool invalidate_old) {
  // We're still waiting for the last request to come back.
  if (!invalidate_old && access_token_request_)
    return;

  OAuth2TokenService::ScopeSet payments_scopes;
  payments_scopes.insert(kPaymentsOAuth2Scope);
  IdentityProvider* identity = delegate_->GetIdentityProvider();
  if (invalidate_old) {
    DCHECK(!access_token_.empty());
    identity->GetTokenService()->InvalidateAccessToken(
        identity->GetActiveAccountId(), payments_scopes, access_token_);
  }
  access_token_.clear();
  access_token_request_ = identity->GetTokenService()->StartRequest(
      identity->GetActiveAccountId(), payments_scopes, this);
}

void PaymentsClient::SetOAuth2TokenAndStartRequest() {
  url_fetcher_->AddExtraRequestHeader(net::HttpRequestHeaders::kAuthorization +
                                      std::string(": Bearer ") + access_token_);

  url_fetcher_->Start();
}

}  // namespace payments
}  // namespace autofill