// Copyright 2018 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_assistant/browser/actions/autofill_action.h"

#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/callback.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill_assistant/browser/actions/action_delegate.h"
#include "components/autofill_assistant/browser/batch_element_checker.h"
#include "components/autofill_assistant/browser/client_memory.h"

namespace autofill_assistant {

AutofillAction::AutofillAction(const ActionProto& proto)
    : Action(proto), weak_ptr_factory_(this) {
  if (proto.has_use_address()) {
    is_autofill_card_ = false;
    prompt_ = proto.use_address().prompt();
    name_ = proto.use_address().name();
    selector_ = Selector(proto.use_address().form_field_element());
    fill_form_message_ = proto.use_address().strings().fill_form();
    check_form_message_ = proto.use_address().strings().check_form();
    required_fields_value_status_.resize(
        proto_.use_address().required_fields_size(), UNKNOWN);
  } else {
    DCHECK(proto.has_use_card());
    is_autofill_card_ = true;
    prompt_ = proto.use_card().prompt();
    name_ = "";
    selector_ = Selector(proto.use_card().form_field_element());
    fill_form_message_ = proto.use_card().strings().fill_form();
    check_form_message_ = proto.use_card().strings().check_form();
  }
  DCHECK(!selector_.empty());
}

AutofillAction::~AutofillAction() = default;

void AutofillAction::InternalProcessAction(
    ActionDelegate* delegate,
    ProcessActionCallback action_callback) {
  process_action_callback_ = std::move(action_callback);

  // Ensure data already selected in a previous action.
  DCHECK(
      (is_autofill_card_ && delegate->GetClientMemory()->has_selected_card()) ||
      (!is_autofill_card_ &&
       delegate->GetClientMemory()->has_selected_address(name_)));

  bool has_valid_data =
      (is_autofill_card_ && delegate->GetClientMemory()->selected_card()) ||
      (!is_autofill_card_ &&
       delegate->GetClientMemory()->selected_address(name_));
  if (!has_valid_data) {
    // User selected 'Fill manually'.
    // TODO(crbug.com/806868): Check whether it is still possible to reach this
    // part of the code.
    delegate->StopCurrentScriptAndShutdown(fill_form_message_);
    EndAction(MANUAL_FALLBACK);
    return;
  }

  FillFormWithData(delegate);
}

void AutofillAction::EndAction(ProcessedActionStatusProto status) {
  UpdateProcessedAction(status);
  std::move(process_action_callback_).Run(std::move(processed_action_proto_));
}

void AutofillAction::FillFormWithData(ActionDelegate* delegate) {
  delegate->ShortWaitForElementExist(
      selector_, base::BindOnce(&AutofillAction::OnWaitForElement,
                                weak_ptr_factory_.GetWeakPtr(),
                                base::Unretained(delegate)));
}

void AutofillAction::OnWaitForElement(ActionDelegate* delegate,
                                      bool element_found) {
  if (!element_found) {
    EndAction(ELEMENT_RESOLUTION_FAILED);
    return;
  }

  DCHECK(!selector_.empty());
  if (is_autofill_card_) {
    delegate->GetFullCard(base::BindOnce(&AutofillAction::OnGetFullCard,
                                         weak_ptr_factory_.GetWeakPtr(),
                                         base::Unretained(delegate)));
    return;
  }

  const autofill::AutofillProfile* profile =
      delegate->GetClientMemory()->selected_address(name_);
  DCHECK(profile);
  delegate->FillAddressForm(profile, selector_,
                            base::BindOnce(&AutofillAction::OnAddressFormFilled,
                                           weak_ptr_factory_.GetWeakPtr(),
                                           base::Unretained(delegate)));
}

void AutofillAction::OnGetFullCard(ActionDelegate* delegate,
                                   std::unique_ptr<autofill::CreditCard> card,
                                   const base::string16& cvc) {
  if (!card) {
    // Gracefully shutdown the script. The empty message forces the use of the
    // default message.
    delegate->StopCurrentScriptAndShutdown("");
    EndAction(MANUAL_FALLBACK);
    return;
  }

  delegate->FillCardForm(std::move(card), cvc, selector_,
                         base::BindOnce(&AutofillAction::OnCardFormFilled,
                                        weak_ptr_factory_.GetWeakPtr()));
}

void AutofillAction::OnCardFormFilled(bool successful) {
  // TODO(crbug.com/806868): Implement required fields checking for cards.
  EndAction(successful ? ACTION_APPLIED : OTHER_ACTION_STATUS);
  return;
}

void AutofillAction::OnAddressFormFilled(ActionDelegate* delegate,
                                         bool successful) {
  // In case Autofill failed, we fail the action.
  if (!successful) {
    EndAction(OTHER_ACTION_STATUS);
    return;
  }

  CheckRequiredFields(delegate, /* allow_fallback */ true);
}

void AutofillAction::CheckRequiredFields(ActionDelegate* delegate,
                                         bool allow_fallback) {
  // If there are no required fields, finish the action successfully.
  if (proto_.use_address().required_fields().empty()) {
    EndAction(ACTION_APPLIED);
    return;
  }

  DCHECK(!batch_element_checker_);
  batch_element_checker_ = delegate->CreateBatchElementChecker();
  for (int i = 0; i < proto_.use_address().required_fields_size(); i++) {
    auto& required_address_field = proto_.use_address().required_fields(i);
    DCHECK_GT(required_address_field.element().selectors_size(), 0);
    batch_element_checker_->AddFieldValueCheck(
        Selector(required_address_field.element()),
        base::BindOnce(&AutofillAction::OnGetRequiredFieldValue,
                       weak_ptr_factory_.GetWeakPtr(), i));
  }
  batch_element_checker_->Run(
      base::TimeDelta::FromSeconds(0),
      /* try_done= */ base::DoNothing(),
      /* all_done= */
      base::BindOnce(&AutofillAction::OnCheckRequiredFieldsDone,
                     weak_ptr_factory_.GetWeakPtr(), base::Unretained(delegate),
                     allow_fallback));
}

void AutofillAction::OnGetRequiredFieldValue(int required_fields_index,
                                             bool exists,
                                             const std::string& value) {
  required_fields_value_status_[required_fields_index] =
      value.empty() ? EMPTY : NOT_EMPTY;
}

void AutofillAction::OnCheckRequiredFieldsDone(ActionDelegate* delegate,
                                               bool allow_fallback) {
  batch_element_checker_.reset();

  // We process all fields with an empty value in order to perform the fallback
  // on all those fields, if any.
  bool validation_successful = true;
  for (FieldValueStatus status : required_fields_value_status_) {
    if (status == EMPTY) {
      validation_successful = false;
      break;
    }
  }

  if (validation_successful) {
    EndAction(ACTION_APPLIED);
    return;
  }

  if (!allow_fallback) {
    // Validation failed and we don't want to try the fallback, so we stop
    // the script.
    delegate->StopCurrentScriptAndShutdown(check_form_message_);
    EndAction(MANUAL_FALLBACK);
    return;
  }

  // If there are any fallbacks for the empty fields, set them, otherwise fail
  // immediately.
  bool has_fallbacks = false;
  auto* profile = delegate->GetClientMemory()->selected_address(name_);
  DCHECK(profile);
  for (int i = 0; i < proto_.use_address().required_fields_size(); i++) {
    if (required_fields_value_status_[i] == EMPTY &&
        !GetAddressFieldValue(
             profile, proto_.use_address().required_fields(i).address_field())
             .empty()) {
      has_fallbacks = true;
      break;
    }
  }
  if (!has_fallbacks) {
    delegate->StopCurrentScriptAndShutdown(check_form_message_);
    EndAction(MANUAL_FALLBACK);
    return;
  }

  // Set the fallback values and check again.
  SetFallbackFieldValuesSequentially(delegate, 0);
}

void AutofillAction::SetFallbackFieldValuesSequentially(
    ActionDelegate* delegate,
    int required_fields_index) {
  DCHECK_GE(required_fields_index, 0);

  // Skip non-empty fields.
  const auto& required_fields = proto_.use_address().required_fields();
  while (required_fields_index < required_fields.size() &&
         required_fields_value_status_[required_fields_index] != EMPTY) {
    required_fields_index++;
  }

  // If there are no more fields to set, check the required fields again,
  // but this time we don't want to try the fallback in case of failure.
  if (required_fields_index >= required_fields.size()) {
    DCHECK_EQ(required_fields_index, required_fields.size());

    CheckRequiredFields(delegate, /* allow_fallback */ false);
    return;
  }

  // Set the next field to its fallback value.
  std::string fallback_value = base::UTF16ToUTF8(GetAddressFieldValue(
      delegate->GetClientMemory()->selected_address(name_),
      required_fields.Get(required_fields_index).address_field()));
  if (fallback_value.empty()) {
    // If there is no fallback value, we skip this failed field.
    SetFallbackFieldValuesSequentially(delegate, ++required_fields_index);
    return;
  }

  DCHECK_GT(
      required_fields.Get(required_fields_index).element().selectors_size(), 0);
  delegate->SetFieldValue(
      Selector(required_fields.Get(required_fields_index).element()),
      fallback_value,
      required_fields.Get(required_fields_index).simulate_key_presses(),
      base::BindOnce(&AutofillAction::OnSetFallbackFieldValue,
                     weak_ptr_factory_.GetWeakPtr(), delegate,
                     required_fields_index));
}

void AutofillAction::OnSetFallbackFieldValue(ActionDelegate* delegate,
                                             int required_fields_index,
                                             bool successful) {
  if (!successful) {
    // Fallback failed: we stop the script without checking the fields.
    delegate->StopCurrentScriptAndShutdown(check_form_message_);
    EndAction(MANUAL_FALLBACK);
    return;
  }
  SetFallbackFieldValuesSequentially(delegate, ++required_fields_index);
}

base::string16 AutofillAction::GetAddressFieldValue(
    const autofill::AutofillProfile* profile,
    const UseAddressProto::RequiredField::AddressField& address_field) {
  // TODO(crbug.com/806868): Get the actual application locale.
  std::string app_locale = "en-US";
  switch (address_field) {
    case UseAddressProto::RequiredField::FIRST_NAME:
      return profile->GetInfo(autofill::NAME_FIRST, app_locale);
    case UseAddressProto::RequiredField::LAST_NAME:
      return profile->GetInfo(autofill::NAME_LAST, app_locale);
    case UseAddressProto::RequiredField::FULL_NAME:
      return profile->GetInfo(autofill::NAME_FULL, app_locale);
    case UseAddressProto::RequiredField::PHONE_NUMBER:
      return profile->GetInfo(autofill::PHONE_HOME_WHOLE_NUMBER, app_locale);
    case UseAddressProto::RequiredField::EMAIL:
      return profile->GetInfo(autofill::EMAIL_ADDRESS, app_locale);
    case UseAddressProto::RequiredField::ORGANIZATION:
      return profile->GetInfo(autofill::COMPANY_NAME, app_locale);
    case UseAddressProto::RequiredField::COUNTRY_CODE:
      return profile->GetInfo(autofill::ADDRESS_HOME_COUNTRY, app_locale);
    case UseAddressProto::RequiredField::REGION:
      return profile->GetInfo(autofill::ADDRESS_HOME_STATE, app_locale);
    case UseAddressProto::RequiredField::STREET_ADDRESS:
      return profile->GetInfo(autofill::ADDRESS_HOME_STREET_ADDRESS,
                              app_locale);
    case UseAddressProto::RequiredField::LOCALITY:
      return profile->GetInfo(autofill::ADDRESS_HOME_CITY, app_locale);
    case UseAddressProto::RequiredField::DEPENDANT_LOCALITY:
      return profile->GetInfo(autofill::ADDRESS_HOME_DEPENDENT_LOCALITY,
                              app_locale);
    case UseAddressProto::RequiredField::POSTAL_CODE:
      return profile->GetInfo(autofill::ADDRESS_HOME_ZIP, app_locale);
    case UseAddressProto::RequiredField::UNDEFINED:
      NOTREACHED();
      return base::string16();
  }
}
}  // namespace autofill_assistant
