// Copyright 2017 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/autofill_profile_validation_util.h"

#include <string>
#include <utility>

#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/address_i18n.h"
#include "components/autofill/core/browser/country_data.h"
#include "components/autofill/core/browser/validation.h"
#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_validator.h"
#include "third_party/libphonenumber/dist/cpp/src/phonenumbers/phonenumberutil.h"

namespace autofill {

namespace {

using ::i18n::addressinput::COUNTRY;
using ::i18n::addressinput::ADMIN_AREA;
using ::i18n::addressinput::LOCALITY;
using ::i18n::addressinput::DEPENDENT_LOCALITY;
using ::i18n::addressinput::SORTING_CODE;
using ::i18n::addressinput::POSTAL_CODE;
using ::i18n::addressinput::STREET_ADDRESS;
using ::i18n::addressinput::RECIPIENT;

using ::i18n::addressinput::AddressData;
using ::i18n::addressinput::AddressField;
using ::i18n::addressinput::AddressProblem;
using ::i18n::addressinput::FieldProblemMap;

using ::i18n::addressinput::INVALID_FORMAT;
using ::i18n::addressinput::MISMATCHING_VALUE;
using ::i18n::addressinput::MISSING_REQUIRED_FIELD;
using ::i18n::addressinput::UNEXPECTED_FIELD;
using ::i18n::addressinput::UNKNOWN_VALUE;

using ::i18n::phonenumbers::PhoneNumberUtil;

const AddressField kFields[] = {COUNTRY, ADMIN_AREA, LOCALITY,
                                DEPENDENT_LOCALITY, POSTAL_CODE};
const AddressProblem kProblems[] = {UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD,
                                    UNKNOWN_VALUE, INVALID_FORMAT,
                                    MISMATCHING_VALUE};

// If the |address_field| is valid, set the validity state of the
// |address_field| in the |profile| to the |state| and return true.
// Otherwise, return false.
bool SetValidityStateForAddressField(const AutofillProfile* profile,
                                     AddressField address_field,
                                     AutofillProfile::ValidityState state) {
  ServerFieldType server_field = i18n::TypeForField(address_field,
                                                    /*billing=*/false);
  if (server_field == UNKNOWN_TYPE)
    return false;
  DCHECK(profile);
  profile->SetValidityState(server_field, state, AutofillProfile::CLIENT);
  return true;
}

// Set the validity state of all address fields in the |profile| to |state|.
void SetAllAddressValidityStates(const AutofillProfile* profile,
                                 AutofillProfile::ValidityState state) {
  DCHECK(profile);
  for (auto field : kFields)
    SetValidityStateForAddressField(profile, field, state);
}

// Returns all relevant pairs of (field, problem), where field is in
// |kFields|, and problem is in |kProblems|.
FieldProblemMap* CreateFieldProblemMap() {
  FieldProblemMap* filter = new FieldProblemMap();
  for (auto field : kFields) {
    for (auto problem : kProblems) {
      filter->insert(std::make_pair(field, problem));
    }
  }
  return filter;
}

// GetFilter() will make sure that the validation only returns problems that
// are relevant.
const FieldProblemMap* GetFilter() {
  static const FieldProblemMap* const filter = CreateFieldProblemMap();
  return filter;
}

// Initializes |address| data from the address info in the |profile|.
void InitializeAddressFromProfile(const AutofillProfile& profile,
                                  AddressData* address) {
  address->region_code =
      base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_COUNTRY));
  address->administrative_area =
      base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_STATE));
  address->locality = base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_CITY));
  address->dependent_locality =
      base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_DEPENDENT_LOCALITY));
  // The validation is case insensitive, and the postal codes are always upper
  // case.
  address->postal_code = base::UTF16ToUTF8(
      base::i18n::ToUpper(profile.GetRawInfo(ADDRESS_HOME_ZIP)));
}

void SetEmptyValidityIfEmpty(const AutofillProfile* profile) {
  if (profile->GetRawInfo(ADDRESS_HOME_COUNTRY).empty())
    profile->SetValidityState(ADDRESS_HOME_COUNTRY, AutofillProfile::EMPTY,
                              AutofillProfile::CLIENT);
  if (profile->GetRawInfo(ADDRESS_HOME_STATE).empty())
    profile->SetValidityState(ADDRESS_HOME_STATE, AutofillProfile::EMPTY,
                              AutofillProfile::CLIENT);
  if (profile->GetRawInfo(ADDRESS_HOME_CITY).empty())
    profile->SetValidityState(ADDRESS_HOME_CITY, AutofillProfile::EMPTY,
                              AutofillProfile::CLIENT);
  if (profile->GetRawInfo(ADDRESS_HOME_DEPENDENT_LOCALITY).empty())
    profile->SetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
                              AutofillProfile::EMPTY, AutofillProfile::CLIENT);
  if (profile->GetRawInfo(ADDRESS_HOME_ZIP).empty())
    profile->SetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::EMPTY,
                              AutofillProfile::CLIENT);
}

void SetInvalidIfUnvalidated(const AutofillProfile* profile) {
  if (profile->GetValidityState(ADDRESS_HOME_COUNTRY,
                                AutofillProfile::CLIENT) ==
      AutofillProfile::UNVALIDATED) {
    profile->SetValidityState(ADDRESS_HOME_COUNTRY, AutofillProfile::INVALID,
                              AutofillProfile::CLIENT);
  }

  if (profile->GetValidityState(ADDRESS_HOME_STATE, AutofillProfile::CLIENT) ==
      AutofillProfile::UNVALIDATED) {
    profile->SetValidityState(ADDRESS_HOME_STATE, AutofillProfile::INVALID,
                              AutofillProfile::CLIENT);
  }

  if (profile->GetValidityState(ADDRESS_HOME_CITY, AutofillProfile::CLIENT) ==
      AutofillProfile::UNVALIDATED) {
    profile->SetValidityState(ADDRESS_HOME_CITY, AutofillProfile::INVALID,
                              AutofillProfile::CLIENT);
  }

  if (profile->GetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
                                AutofillProfile::CLIENT) ==
      AutofillProfile::UNVALIDATED) {
    profile->SetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
                              AutofillProfile::INVALID,
                              AutofillProfile::CLIENT);
  }

  if (profile->GetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::CLIENT) ==
      AutofillProfile::UNVALIDATED) {
    profile->SetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::INVALID,
                              AutofillProfile::CLIENT);
  }
}

void MaybeApplyValidToFields(const AutofillProfile* profile) {
  // The metadata works from top to bottom. Therefore, a so far UNVALIDATED
  // subregion can only be validated if its super-region is VALID. In  this
  // case, it's VALID if it has not been marked as INVALID or EMPTY.

  if (profile->GetValidityState(ADDRESS_HOME_STATE, AutofillProfile::CLIENT) ==
      AutofillProfile::UNVALIDATED) {
    profile->SetValidityState(ADDRESS_HOME_STATE, AutofillProfile::VALID,
                              AutofillProfile::CLIENT);
  }

  if (profile->GetValidityState(ADDRESS_HOME_CITY, AutofillProfile::CLIENT) ==
          AutofillProfile::UNVALIDATED &&
      profile->GetValidityState(ADDRESS_HOME_STATE, AutofillProfile::CLIENT) ==
          AutofillProfile::VALID) {
    profile->SetValidityState(ADDRESS_HOME_CITY, AutofillProfile::VALID,
                              AutofillProfile::CLIENT);
  }

  if (profile->GetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
                                AutofillProfile::CLIENT) ==
          AutofillProfile::UNVALIDATED &&
      profile->GetValidityState(ADDRESS_HOME_CITY, AutofillProfile::CLIENT) ==
          AutofillProfile::VALID) {
    profile->SetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
                              AutofillProfile::VALID, AutofillProfile::CLIENT);
  }

  // ZIP only depends on COUNTRY. If it's not so far marked as INVALID or EMPTY,
  // then it's VALID.
  if (profile->GetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::CLIENT) ==
      AutofillProfile::UNVALIDATED) {
    profile->SetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::VALID,
                              AutofillProfile::CLIENT);
  }
}

void ApplyValidOnlyIfAllChildrenNotInvalid(const AutofillProfile* profile) {
  if (profile->GetValidityState(ADDRESS_HOME_STATE, AutofillProfile::CLIENT) ==
          AutofillProfile::INVALID &&
      profile->GetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::CLIENT) ==
          AutofillProfile::INVALID) {
    profile->SetValidityState(ADDRESS_HOME_COUNTRY, AutofillProfile::INVALID,
                              AutofillProfile::CLIENT);
  }

  if (profile->GetValidityState(ADDRESS_HOME_CITY, AutofillProfile::CLIENT) ==
      AutofillProfile::INVALID) {
    profile->SetValidityState(ADDRESS_HOME_STATE, AutofillProfile::INVALID,
                              AutofillProfile::CLIENT);
  }

  if (profile->GetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
                                AutofillProfile::CLIENT) ==
      AutofillProfile::INVALID) {
    profile->SetValidityState(ADDRESS_HOME_CITY, AutofillProfile::INVALID,
                              AutofillProfile::CLIENT);
  }
}

}  // namespace

namespace profile_validation_util {

void ValidateProfile(const AutofillProfile* profile,
                     AddressValidator* address_validator) {
  DCHECK(address_validator);
  DCHECK(profile);
  ValidateAddressStrictly(profile, address_validator);
  ValidatePhoneNumber(profile);
  ValidateEmailAddress(profile);
}

AddressValidator::Status ValidateAddress(const AutofillProfile* profile,
                                         AddressValidator* address_validator) {
  DCHECK(address_validator);
  DCHECK(profile);

  SetAllAddressValidityStates(profile, AutofillProfile::UNVALIDATED);

  if (!base::ContainsValue(
          CountryDataMap::GetInstance()->country_codes(),
          base::UTF16ToUTF8(profile->GetRawInfo(ADDRESS_HOME_COUNTRY)))) {
    // If the country code is not in the database, the country code and the
    // profile are invalid, and other fields cannot be validated, because it is
    // unclear which, if any, rule should apply.
    SetValidityStateForAddressField(profile, COUNTRY, AutofillProfile::INVALID);
    SetEmptyValidityIfEmpty(profile);
    return AddressValidator::SUCCESS;
  }

  // The COUNTRY was already listed in the CountryDataMap, therefore it's valid.
  SetValidityStateForAddressField(profile, COUNTRY, AutofillProfile::VALID);

  AddressData address;
  InitializeAddressFromProfile(*profile, &address);
  FieldProblemMap problems;
  // status denotes if the rule was successfully loaded before validation.
  AddressValidator::Status status =
      address_validator->ValidateAddress(address, GetFilter(), &problems);

  for (auto problem : problems)
    SetValidityStateForAddressField(profile, problem.first,
                                    AutofillProfile::INVALID);

  SetEmptyValidityIfEmpty(profile);

  // Fields (except COUNTRY) could be VALID, only if the rules were available.
  if (status == AddressValidator::SUCCESS)
    MaybeApplyValidToFields(profile);

  return status;
}

void ValidateAddressStrictly(const AutofillProfile* profile,
                             AddressValidator* address_validator) {
  DCHECK(address_validator);
  DCHECK(profile);

  // If the rules were loaded successfully, add a second layer of validation:
  // 1. For a field to stay valid after the first run, all the fields that
  // depend on that field for validation need to not be invalid on the first
  // run, otherwise there is a chance that the data on that field was also
  // invalid (incorrect.)
  // Example: 1225 Notre-Dame Ouest, Montreal, Quebec, H3C 2A3, United States.
  // A human validator can see that the country is most probably the invalid
  // field. The first step helps us validate the rules interdependently.
  // 2. All the address fields that could not be validated (UNVALIDATED),
  // should be considered as invalid.

  if (ValidateAddress(profile, address_validator) ==
      AddressValidator::SUCCESS) {
    ApplyValidOnlyIfAllChildrenNotInvalid(profile);
    SetInvalidIfUnvalidated(profile);
  }
}

void ValidateEmailAddress(const AutofillProfile* profile) {
  const base::string16& email = profile->GetRawInfo(EMAIL_ADDRESS);
  if (email.empty()) {
    profile->SetValidityState(EMAIL_ADDRESS, AutofillProfile::EMPTY,
                              AutofillProfile::CLIENT);
    return;
  }

  profile->SetValidityState(EMAIL_ADDRESS,
                            autofill::IsValidEmailAddress(email)
                                ? AutofillProfile::VALID
                                : AutofillProfile::INVALID,
                            AutofillProfile::CLIENT);
}

void ValidatePhoneNumber(const AutofillProfile* profile) {
  const std::string& phone_number =
      base::UTF16ToUTF8(profile->GetRawInfo(PHONE_HOME_WHOLE_NUMBER));
  if (phone_number.empty()) {
    profile->SetValidityState(PHONE_HOME_WHOLE_NUMBER, AutofillProfile::EMPTY,
                              AutofillProfile::CLIENT);
    return;
  }

  const std::string& country_code =
      base::UTF16ToUTF8(profile->GetRawInfo(ADDRESS_HOME_COUNTRY));
  if (!base::ContainsValue(CountryDataMap::GetInstance()->country_codes(),
                           country_code)) {
    // If the country code is not in the database, the phone number cannot be
    // validated.
    profile->SetValidityState(PHONE_HOME_WHOLE_NUMBER,
                              AutofillProfile::UNVALIDATED,
                              AutofillProfile::CLIENT);
    return;
  }

  PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance();
  profile->SetValidityState(
      PHONE_HOME_WHOLE_NUMBER,
      phone_util->IsPossibleNumberForString(phone_number, country_code)
          ? AutofillProfile::VALID
          : AutofillProfile::INVALID,
      AutofillProfile::CLIENT);
}

}  // namespace profile_validation_util
}  // namespace autofill
