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

#include "chrome/browser/ui/views/autofill/edit_address_profile_view.h"

#include <memory>
#include <utility>

#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/autofill/edit_address_profile_dialog_controller.h"
#include "chrome/browser/ui/views/autofill/address_editor_view.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "components/autofill/core/browser/data_model/addresses/autofill_i18n_api.h"
#include "components/autofill/core/browser/data_model/addresses/autofill_profile.h"
#include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/views/widget/widget.h"

namespace autofill {

// |arg| must be of type base::optional_ref<const AutofillProfile>.
MATCHER_P2(AutofillProfileHasInfo, type, expected_value, "") {
  EXPECT_TRUE(arg.has_value());
  return arg.value().GetRawInfo(type) == expected_value;
}

class MockEditAddressProfileDialogController
    : public EditAddressProfileDialogController {
 public:
  MOCK_METHOD(std::u16string, GetWindowTitle, (), (const, override));
  MOCK_METHOD(const std::u16string&, GetFooterMessage, (), (const, override));
  MOCK_METHOD(std::u16string, GetOkButtonLabel, (), (const, override));
  MOCK_METHOD(const AutofillProfile&, GetProfileToEdit, (), (const, override));
  MOCK_METHOD(bool, GetIsValidatable, (), (const, override));
  MOCK_METHOD(void,
              OnDialogClosed,
              (AutofillClient::AddressPromptUserDecision decision,
               base::optional_ref<const AutofillProfile> profile),
              (override));
};

class EditAddressProfileViewTest : public ChromeViewsTestBase {
 public:
  EditAddressProfileViewTest() = default;
  ~EditAddressProfileViewTest() override = default;

  void CreateViewAndShow() { CreateViewAndShow(address_profile_to_edit()); }

  void CreateViewAndShow(const AutofillProfile& address_profile);

  void SetUp() override {
    ChromeViewsTestBase::SetUp();

    address_profile_to_edit_ = test::GetFullProfile();
    test_web_contents_ =
        content::WebContentsTester::CreateTestWebContents(&profile_, nullptr);

    ON_CALL(mock_controller_, GetFooterMessage)
        .WillByDefault(::testing::ReturnRefOfCopy(std::u16string()));
  }

  void TearDown() override {
    if (widget_) {
      widget_->Close();
    }
    parent_widget_.reset();
    ChromeViewsTestBase::TearDown();
  }

  void WidgetClosed(views::Widget::ClosedReason closed_reason);

  const AutofillProfile& address_profile_to_edit() {
    return address_profile_to_edit_;
  }
  content::WebContents* test_web_contents() { return test_web_contents_.get(); }
  EditAddressProfileView* dialog() { return dialog_; }
  MockEditAddressProfileDialogController* mock_controller() {
    return &mock_controller_;
  }

 private:
  base::test::ScopedFeatureList feature_list_;
  TestingProfile profile_;
  AutofillProfile address_profile_to_edit_{
      i18n_model_definition::kLegacyHierarchyCountryCode};
  // This enables uses of TestWebContents.
  content::RenderViewHostTestEnabler test_render_host_factories_;
  std::unique_ptr<content::WebContents> test_web_contents_;
  std::unique_ptr<views::Widget> parent_widget_;
  std::unique_ptr<views::Widget> widget_ = nullptr;
  raw_ptr<EditAddressProfileView> dialog_ = nullptr;
  testing::NiceMock<MockEditAddressProfileDialogController> mock_controller_;
};

void EditAddressProfileViewTest::WidgetClosed(
    views::Widget::ClosedReason closed_reason) {
  dialog_->WidgetClosed();
  dialog_ = nullptr;
  std::exchange(widget_, nullptr).reset();
}

void EditAddressProfileViewTest::CreateViewAndShow(
    const AutofillProfile& address_profile) {
  ON_CALL(*mock_controller(), GetWindowTitle())
      .WillByDefault(testing::Return(std::u16string()));
  ON_CALL(*mock_controller(), GetProfileToEdit())
      .WillByDefault(testing::ReturnRef(address_profile));

  dialog_ = new EditAddressProfileView(mock_controller());
  dialog_->ShowForWebContents(test_web_contents());

  gfx::NativeView parent = gfx::NativeView();
#if BUILDFLAG(IS_MAC)
  // We need a native view parent for the dialog to avoid a DCHECK
  // on Mac.
  parent_widget_ =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
  parent = parent_widget_->GetNativeView();
#endif
  widget_ = base::WrapUnique(
      views::DialogDelegate::CreateDialogWidget(dialog_, GetContext(), parent));
  widget_->SetVisibilityChangedAnimationsEnabled(false);
  widget_->MakeCloseSynchronous(base::BindOnce(
      &EditAddressProfileViewTest::WidgetClosed, base::Unretained(this)));
  widget_->Show();
#if BUILDFLAG(IS_MAC)
  // Necessary for Mac. On other platforms this happens in the focus
  // manager, but it's disabled for Mac due to crbug.com/40486728.
  parent_widget_->Activate();
  widget_->Activate();
#endif
}

TEST_F(EditAddressProfileViewTest, Sanity) {
  CreateViewAndShow();
  // Check that both OK and cancel button are enabled.
  EXPECT_TRUE(dialog()->IsDialogButtonEnabled(ui::mojom::DialogButton::kOk));
  EXPECT_TRUE(
      dialog()->IsDialogButtonEnabled(ui::mojom::DialogButton::kCancel));
}

TEST_F(EditAddressProfileViewTest, SaveInvokesTheCallbackWithEditedFullname) {
  CreateViewAndShow();
  const std::u16string kNewFirstName = u"New First Name";
  const std::string locale = g_browser_process->GetApplicationLocale();
  // Confirm that the new name is indeed different from the original one from
  // the controller.
  ASSERT_NE(kNewFirstName,
            address_profile_to_edit().GetInfo(FieldType::NAME_FULL, locale));
  AddressEditorView* editor_view = dialog()->GetAddressEditorViewForTesting();
  DCHECK(editor_view);

  editor_view->SetTextInputFieldValueForTesting(FieldType::NAME_FULL,
                                                kNewFirstName);

  EXPECT_CALL(*mock_controller(),
              OnDialogClosed(
                  AutofillClient::AddressPromptUserDecision::kEditAccepted,
                  AutofillProfileHasInfo(FieldType::NAME_FULL, kNewFirstName)));
  dialog()->Accept();
}

TEST_F(EditAddressProfileViewTest,
       SaveInvokesTheCallbackWithEditedInvalidPhoneNumber) {
  CreateViewAndShow();
  const std::u16string kNewPhoneNumber = u"123456789";
  const std::string locale = g_browser_process->GetApplicationLocale();
  // Confirm that the new phone number is indeed different from the original one
  // from the controller.
  ASSERT_NE(kNewPhoneNumber, address_profile_to_edit().GetInfo(
                                 FieldType::PHONE_HOME_WHOLE_NUMBER, locale));

  // Set the phone number in the editor to the new invalid value. Make sure that
  // this value is respected and sent to the backend.
  AddressEditorView* editor_view = dialog()->GetAddressEditorViewForTesting();
  DCHECK(editor_view);

  editor_view->SetTextInputFieldValueForTesting(
      FieldType::PHONE_HOME_WHOLE_NUMBER, kNewPhoneNumber);

  EXPECT_CALL(
      *mock_controller(),
      OnDialogClosed(AutofillClient::AddressPromptUserDecision::kEditAccepted,
                     AutofillProfileHasInfo(FieldType::PHONE_HOME_WHOLE_NUMBER,
                                            kNewPhoneNumber)));
  dialog()->Accept();
}

TEST_F(EditAddressProfileViewTest, SaveInvokesTheCallbackWithEditedEmail) {
  CreateViewAndShow();
  const std::u16string kNewEmail = u"abc@xyz.com";
  const std::string locale = g_browser_process->GetApplicationLocale();
  // Confirm that the new email is indeed different from the original one
  // from the controller.
  ASSERT_NE(kNewEmail, address_profile_to_edit().GetInfo(
                           FieldType::EMAIL_ADDRESS, locale));
  AddressEditorView* editor_view = dialog()->GetAddressEditorViewForTesting();
  DCHECK(editor_view);

  editor_view->SetTextInputFieldValueForTesting(FieldType::EMAIL_ADDRESS,
                                                kNewEmail);

  EXPECT_CALL(*mock_controller(),
              OnDialogClosed(
                  AutofillClient::AddressPromptUserDecision::kEditAccepted,
                  AutofillProfileHasInfo(FieldType::EMAIL_ADDRESS, kNewEmail)));
  dialog()->Accept();
}

TEST_F(EditAddressProfileViewTest, InvalidFormIsNotSent) {
  ON_CALL(*mock_controller(), GetIsValidatable)
      .WillByDefault(::testing::Return(true));
  CreateViewAndShow(AutofillProfile(AddressCountryCode("US")));

  // `kIgnored` is sent as the decision when the dialog is closed (on test end).
  EXPECT_CALL(
      *mock_controller(),
      OnDialogClosed(AutofillClient::AddressPromptUserDecision::kIgnored,
                     ::testing::_));
  EXPECT_CALL(
      *mock_controller(),
      OnDialogClosed(AutofillClient::AddressPromptUserDecision::kEditAccepted,
                     ::testing::_))
      .Times(0);

  dialog()->Accept();
}

TEST_F(EditAddressProfileViewTest, GetInitiallyFocusedView) {
  auto dialog = std::make_unique<EditAddressProfileView>(mock_controller());

  EXPECT_EQ(dialog->GetInitiallyFocusedView(), nullptr);

  AutofillProfile profile(AddressCountryCode("US"));
  ON_CALL(*mock_controller(), GetProfileToEdit())
      .WillByDefault(testing::ReturnRef(profile));
  dialog->ShowForWebContents(test_web_contents());

  EXPECT_NE(dialog->GetInitiallyFocusedView(), nullptr);
  EXPECT_EQ(dialog->GetInitiallyFocusedView()->GetClassName(), "Combobox");
}

}  // namespace autofill
