// Copyright 2024 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/android/omnibox/autocomplete_controller_android.h"

#include <memory>
#include <optional>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/page_load_metrics/chrome_initiator_location.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/omnibox/browser/autocomplete_controller_config.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/fake_autocomplete_provider_client.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/page_load_metrics/browser/navigation_handle_user_data.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/test/mock_navigation_handle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "third_party/metrics_proto/omnibox_focus_type.pb.h"
#include "third_party/omnibox_proto/tool_mode.pb.h"
#include "url/gurl.h"

namespace {

// Aliases.
using ::testing::AllOf;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::Conditional;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::NiceMock;
using ::testing::Property;
using ::testing::Values;
using ::testing::WithParamInterface;

// Mocks -----------------------------------------------------------------------

class MockAutocompleteController : public AutocompleteController {
 public:
  MockAutocompleteController()
      : AutocompleteController(
            std::make_unique<FakeAutocompleteProviderClient>(),
            AutocompleteControllerConfig{}) {}

  // AutocompleteController:
  MOCK_METHOD(void, Start, (const AutocompleteInput&), (override));
};

}  // namespace

// AutocompleteControllerAndroidTest -------------------------------------------

// Base class for tests of `AutocompleteControllerAndroid`.
class AutocompleteControllerAndroidTest
    : public ChromeRenderViewHostTestHarness {
 public:
  AutocompleteControllerAndroid* controller() { return controller_; }
  NiceMock<MockAutocompleteController>* mock() { return mock_; }

 private:
  // ChromeRenderViewHostTestHarness:
  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();

    // Initialize autocomplete controller/mock.
    controller_ =
        AutocompleteControllerAndroid::Factory::GetForProfile(profile());
    ASSERT_TRUE(controller_);
    mock_ = controller_->SetAutocompleteControllerForTesting(
        std::make_unique<NiceMock<MockAutocompleteController>>());
  }

  raw_ptr<AutocompleteControllerAndroid> controller_ = nullptr;
  raw_ptr<NiceMock<MockAutocompleteController>> mock_ = nullptr;
};

TEST_F(AutocompleteControllerAndroidTest, OnOmniboxFocused_NTP) {
  using OEP = metrics::OmniboxEventProto;
  using OFT = metrics::OmniboxFocusType;

  GURL url("chrome://newtab");

  auto page_classification = OEP::NTP;

  EXPECT_CALL(
      *mock(),
      Start(AllOf(Property(&AutocompleteInput::text, IsEmpty()),
                  Property(&AutocompleteInput::current_url, Eq(url)),
                  Property(&AutocompleteInput::current_title, Eq(u"title")),
                  Property(&AutocompleteInput::focus_type,
                           Eq(OFT::INTERACTION_FOCUS)))));

  controller()->OnOmniboxFocused(nullptr, u"", url, page_classification,
                                 omnibox::TOOL_MODE_UNSPECIFIED, u"title");
}

TEST_F(AutocompleteControllerAndroidTest, OnOmniboxFocused_OTHER) {
  using OEP = metrics::OmniboxEventProto;
  using OFT = metrics::OmniboxFocusType;

  GURL url("https://site.biz/");

  auto page_classification = OEP::OTHER;

  EXPECT_CALL(
      *mock(),
      Start(AllOf(Property(&AutocompleteInput::text, IsEmpty()),
                  Property(&AutocompleteInput::current_url, Eq(url)),
                  Property(&AutocompleteInput::current_title, Eq(u"title")),
                  Property(&AutocompleteInput::focus_type,
                           Eq(OFT::INTERACTION_FOCUS)))));

  controller()->OnOmniboxFocused(nullptr, u"text", url, page_classification,
                                 omnibox::TOOL_MODE_UNSPECIFIED, u"title");
}

TEST_F(AutocompleteControllerAndroidTest, GetTemplateUrlForText_NotFound) {
  auto result = controller()->GetTemplateUrlForText(u"nonexistent_keyword");
  EXPECT_TRUE(result.is_null());
}

TEST_F(AutocompleteControllerAndroidTest, Start_InKeywordMode) {
  using OEP = metrics::OmniboxEventProto;

  GURL url("https://site.biz/");

  EXPECT_CALL(
      *mock(),
      Start(AllOf(Property(&AutocompleteInput::text, Eq(u"query")),
                  Property(&AutocompleteInput::current_url, Eq(url)),
                  Property(&AutocompleteInput::in_keyword_mode, Eq(true)))));

  controller()->Start(nullptr, u"query", -1, "", url, OEP::OTHER,
                      omnibox::TOOL_MODE_UNSPECIFIED, false, true, false, true);
}

TEST_F(AutocompleteControllerAndroidTest,
       CreateNavigationObserver_OmniboxDirectUrlInput_AttachesUserData) {
  content::MockNavigationHandle navigation_handle(web_contents());
  AutocompleteMatch match;
  match.transition = ui::PAGE_TRANSITION_TYPED;

  controller()->CreateNavigationObserver(
      reinterpret_cast<uintptr_t>(&navigation_handle),
      reinterpret_cast<uintptr_t>(&match));

  auto* user_data =
      page_load_metrics::NavigationHandleUserData::GetForNavigationHandle(
          navigation_handle);
  ASSERT_TRUE(user_data);
  EXPECT_EQ(
      user_data->navigation_type(),
      GetInitiatorLocation(ChromeInitiatorLocation::kOmniboxDirectUrlInput));
}

TEST_F(AutocompleteControllerAndroidTest,
       CreateNavigationObserver_OmniboxDefaultSearchEngine_AttachesUserData) {
  content::MockNavigationHandle navigation_handle(web_contents());
  AutocompleteMatch match;
  match.transition = ui::PAGE_TRANSITION_GENERATED;

  controller()->CreateNavigationObserver(
      reinterpret_cast<uintptr_t>(&navigation_handle),
      reinterpret_cast<uintptr_t>(&match));

  auto* user_data =
      page_load_metrics::NavigationHandleUserData::GetForNavigationHandle(
          navigation_handle);
  ASSERT_TRUE(user_data);
  EXPECT_EQ(user_data->navigation_type(),
            GetInitiatorLocation(
                ChromeInitiatorLocation::kOmniboxDefaultSearchEngine));
}
