// 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/ui/lens/lens_overlay_url_builder.h"

#include "base/base64url.h"
#include "base/strings/escape.h"
#include "base/strings/stringprintf.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/browser_process.h"
#include "components/lens/lens_features.h"
#include "components/lens/lens_url_utils.h"
#include "net/base/url_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/lens_server_proto/lens_overlay_cluster_info.pb.h"
#include "third_party/lens_server_proto/lens_overlay_knowledge_intent_query.pb.h"
#include "third_party/lens_server_proto/lens_overlay_knowledge_query.pb.h"
#include "third_party/lens_server_proto/lens_overlay_request_id.pb.h"
#include "third_party/lens_server_proto/lens_overlay_selection_type.pb.h"
#include "third_party/lens_server_proto/lens_overlay_stickiness_signals.pb.h"
#include "third_party/lens_server_proto/lens_overlay_translate_stickiness_signals.pb.h"
#include "third_party/lens_server_proto/lens_overlay_video_context_input_params.pb.h"
#include "third_party/lens_server_proto/lens_overlay_video_params.pb.h"
#include "third_party/omnibox_proto/search_context.pb.h"
#include "third_party/zlib/google/compression_utils.h"

namespace lens {

constexpr char kResultsSearchBaseUrl[] = "https://www.google.com/search";
constexpr char kResultsRedirectBaseUrl[] = "https://www.google.com/url";
constexpr char kLanguage[] = "en-US";
constexpr char kPageUrl[] = "https://www.google.com";
constexpr char kPageTitle[] = "Page Title";

// Query parameter for the query submission time.
inline constexpr char kQuerySubmissionTimeQueryParameter[] = "qsubts";

// Query parameter for the client upload processing duration.
inline constexpr char kClientUploadDurationQueryParameter[] = "cud";

// The test time.
constexpr base::Time kTestTime = base::Time::FromSecondsSinceUnixEpoch(1000);

// Query parameters to send to translate API for getting supported translate
// languages.
inline constexpr char kCountryQueryParameter[] = "country";
inline constexpr char kDisplayLanguageQueryParameter[] = "display_language";
inline constexpr char kClientIdQueryParameter[] = "client";

class LensOverlayUrlBuilderTest : public testing::Test {
 public:
  void SetUp() override {
    g_browser_process->SetApplicationLocale(kLanguage);
    // Set all the feature params here to keep the test consistent if future
    // default values are changed.
    feature_list_.InitWithFeaturesAndParameters(
        {{lens::features::kLensOverlay,
          {
              {"results-search-url", kResultsSearchBaseUrl},
          }},
         {lens::features::kLensOverlayContextualSearchbox,
          {
              {"use-video-context-for-text-only-requests", "true"},
              {"use-video-context-for-multimodal-requests", "true"},
          }},
         {lens::features::kLensOverlayTranslateLanguages, {}},
         {lens::features::kLensOverlayUpdatedClientContext, {}}},
        /*disabled_features=*/{});
  }

  std::string EncodeRequestId(lens::LensOverlayRequestId* request_id) {
    std::string serialized_request_id;
    EXPECT_TRUE(request_id->SerializeToString(&serialized_request_id));
    std::string encoded_request_id;
    base::Base64UrlEncode(serialized_request_id,
                          base::Base64UrlEncodePolicy::OMIT_PADDING,
                          &encoded_request_id);
    return encoded_request_id;
  }

  // Checks that the query submission time and client upload duration params are
  // present and removes them.
  GURL StripQuerySubmissionTimeAndClientUploadDurationParam(const GURL& url) {
    GURL new_url = url;
    std::string unused_query_submission_time;
    bool has_query_submission_time = net::GetValueForKeyInQuery(
        url, kQuerySubmissionTimeQueryParameter, &unused_query_submission_time);
    EXPECT_TRUE(has_query_submission_time);
    new_url = net::AppendOrReplaceQueryParameter(
        new_url, kQuerySubmissionTimeQueryParameter, std::nullopt);

    std::string unused_client_upload_duration;
    bool has_client_upload_duration =
        net::GetValueForKeyInQuery(url, kClientUploadDurationQueryParameter,
                                   &unused_client_upload_duration);
    EXPECT_TRUE(has_client_upload_duration);
    new_url = net::AppendOrReplaceQueryParameter(
        new_url, kClientUploadDurationQueryParameter, std::nullopt);
    return new_url;
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

TEST_F(LensOverlayUrlBuilderTest, AppendLensOverlaySidePanelParams) {
  std::map<std::string, std::string> params;
  uint64_t gen204_id = 0;

  // Test multimodal
  lens::AppendLensOverlaySidePanelParams(params, gen204_id, /*has_text=*/true,
                                         /*has_image=*/true);
  EXPECT_EQ(params["lns_fp"], "1");
  EXPECT_EQ(params["lns_mode"], "mu");
  EXPECT_EQ(params["plla"], "0");

  params.clear();

  // Test text only
  lens::AppendLensOverlaySidePanelParams(params, gen204_id, /*has_text=*/true,
                                         /*has_image=*/false);
  EXPECT_EQ(params["lns_fp"], "1");
  EXPECT_EQ(params["lns_mode"], "text");
  EXPECT_EQ(params["plla"], "0");

  params.clear();

  // Test image only
  lens::AppendLensOverlaySidePanelParams(params, gen204_id, /*has_text=*/false,
                                         /*has_image=*/true);
  EXPECT_EQ(params["lns_fp"], "1");
  EXPECT_EQ(params["lns_mode"], "un");
  EXPECT_EQ(params["plla"], "0");

  params.clear();

  // Test no input (should not crash, but also not set mode)
  lens::AppendLensOverlaySidePanelParams(params, gen204_id, /*has_text=*/false,
                                         /*has_image=*/false);
  EXPECT_EQ(params["lns_fp"], "1");
  EXPECT_FALSE(params.contains("lns_mode"));
  EXPECT_EQ(params["plla"], "0");
}

TEST_F(LensOverlayUrlBuilderTest, AppendTranslateParamsToMap) {
  std::string query = "test";
  std::map<std::string, std::string> params;

  lens::AppendTranslateParamsToMap(params, query, "auto");

  lens::StickinessSignals expected_proto;
  expected_proto.set_id_namespace(lens::StickinessSignals::TRANSLATE_LITE);
  auto* intent_query = expected_proto.mutable_interpretation()
                           ->mutable_message_set_extension()
                           ->mutable_intent_query();
  intent_query->set_name("Translate");
  intent_query->mutable_signals()
      ->mutable_translate_stickiness_signals()
      ->set_translate_suppress_echo_for_sticky(false);
  auto* text_argument = intent_query->add_argument();
  text_argument->set_name("Text");
  text_argument->mutable_value()->mutable_simple_value()->set_string_value(
      "test");

  std::string compressed_proto;
  ASSERT_TRUE(base::Base64UrlDecode(
      params["stick"], base::Base64UrlDecodePolicy::DISALLOW_PADDING,
      &compressed_proto));
  std::string serialized_proto;
  ASSERT_TRUE(compression::GzipUncompress(compressed_proto, &serialized_proto));
  lens::StickinessSignals stickiness_signals;
  ASSERT_TRUE(stickiness_signals.ParseFromString(serialized_proto));
  EXPECT_THAT(stickiness_signals, base::test::EqualsProto(expected_proto));
}

TEST_F(LensOverlayUrlBuilderTest, AppendStickinessSignalForFormula) {
  std::string query = "(x + 2) / 4 = 4";
  std::string formula = "\\frac{x + 2}{4} = 4";
  std::map<std::string, std::string> params;

  lens::AppendStickinessSignalForFormula(params, formula);

  lens::StickinessSignals expected_proto;
  expected_proto.set_id_namespace(lens::StickinessSignals::EDUCATION_INPUT);
  expected_proto.mutable_education_input_extension()
      ->mutable_math_solver_query()
      ->set_math_input_equation(formula);

  std::string compressed_proto;
  ASSERT_TRUE(base::Base64UrlDecode(
      params["stick"], base::Base64UrlDecodePolicy::DISALLOW_PADDING,
      &compressed_proto));
  std::string serialized_proto;
  ASSERT_TRUE(compression::GzipUncompress(compressed_proto, &serialized_proto));
  lens::StickinessSignals stickiness_signals;
  ASSERT_TRUE(stickiness_signals.ParseFromString(serialized_proto));
  EXPECT_THAT(stickiness_signals, base::test::EqualsProto(expected_proto));
}

TEST_F(LensOverlayUrlBuilderTest, BuildTextOnlySearchURL) {
  std::string text_query = "Apples";
  std::map<std::string, std::string> additional_params;
  std::string expected_url =
      base::StringPrintf("%s?source=chrome.cr.menu&q=%s&cs=0&gsc=2&hl=%s",
                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage);

  EXPECT_EQ(StripQuerySubmissionTimeAndClientUploadDurationParam(
                lens::BuildTextOnlySearchURL(
                    kTestTime, text_query,
                    /*page_url=*/std::nullopt,
                    /*page_title=*/std::nullopt, additional_params,
                    lens::LensOverlayInvocationSource::kAppMenu,
                    lens::LensOverlaySelectionType::UNKNOWN_SELECTION_TYPE,
                    /*use_dark_mode=*/false)),
            expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildTextOnlySearchURLForLensTextSelection) {
  std::string text_query = "Apples";
  std::map<std::string, std::string> additional_params;
  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&q=%s&lns_fp=1&lns_mode=text&lns_surface=42&cs="
      "0&gsc=2&hl=%s",
      kResultsSearchBaseUrl, text_query.c_str(), kLanguage);

  EXPECT_EQ(StripQuerySubmissionTimeAndClientUploadDurationParam(
                lens::BuildTextOnlySearchURL(
                    kTestTime, text_query,
                    /*page_url=*/std::nullopt,
                    /*page_title=*/std::nullopt, additional_params,
                    lens::LensOverlayInvocationSource::kAppMenu,
                    lens::LensOverlaySelectionType::SELECT_TEXT_HIGHLIGHT,
                    /*use_dark_mode=*/false)),
            expected_url);
}

TEST_F(LensOverlayUrlBuilderTest,
       BuildTextOnlySearchURLWithVideoContextFlagOff) {
  feature_list_.Reset();
  feature_list_.InitWithFeaturesAndParameters(
      {{lens::features::kLensOverlay,
        {
            {"results-search-url", kResultsSearchBaseUrl},
        }},
       {lens::features::kLensOverlayContextualSearchbox,
        {
            {"use-video-context-for-text-only-requests", "false"},
        }}},
      /*disabled_features=*/{});

  std::string text_query = "Apples";
  std::map<std::string, std::string> additional_params;

  std::string expected_url =
      base::StringPrintf("%s?source=chrome.cr.menu&q=%s&cs=0&gsc=2&hl=%s",
                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage);

  EXPECT_EQ(
      StripQuerySubmissionTimeAndClientUploadDurationParam(
          lens::BuildTextOnlySearchURL(
              kTestTime, text_query, std::make_optional<GURL>(kPageUrl),
              std::make_optional<std::string>(kPageTitle), additional_params,
              lens::LensOverlayInvocationSource::kAppMenu,
              lens::LensOverlaySelectionType::UNKNOWN_SELECTION_TYPE,
              /*use_dark_mode=*/false)),
      expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildTextOnlySearchURLWithPageUrlAndTitle) {
  std::string text_query = "Apples";
  std::map<std::string, std::string> additional_params;

  std::string expected_url =
      base::StringPrintf("%s?source=chrome.cr.menu&q=%s&cs=0&gsc=2&hl=%s",
                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage);

  EXPECT_EQ(
      StripQuerySubmissionTimeAndClientUploadDurationParam(
          lens::BuildTextOnlySearchURL(
              kTestTime, text_query, std::make_optional<GURL>(kPageUrl),
              std::make_optional<std::string>(kPageTitle), additional_params,
              lens::LensOverlayInvocationSource::kAppMenu,
              lens::LensOverlaySelectionType::UNKNOWN_SELECTION_TYPE,
              /*use_dark_mode=*/false)),
      expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildTextOnlySearchURLWithPageUrl) {
  std::string text_query = "Apples";
  std::map<std::string, std::string> additional_params;

  std::string expected_url =
      base::StringPrintf("%s?source=chrome.cr.menu&q=%s&cs=0&gsc=2&hl=%s",
                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage);

  EXPECT_EQ(StripQuerySubmissionTimeAndClientUploadDurationParam(
                lens::BuildTextOnlySearchURL(
                    kTestTime, text_query, std::make_optional<GURL>(kPageUrl),
                    /*page_title=*/std::nullopt, additional_params,
                    lens::LensOverlayInvocationSource::kAppMenu,
                    lens::LensOverlaySelectionType::UNKNOWN_SELECTION_TYPE,
                    /*use_dark_mode=*/false)),
            expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildTextOnlySearchURLEmpty) {
  std::string text_query = "";
  std::map<std::string, std::string> additional_params;
  std::string expected_url =
      base::StringPrintf("%s?source=chrome.cr.menu&q=&cs=0&gsc=2&hl=%s",
                         kResultsSearchBaseUrl, kLanguage);

  EXPECT_EQ(StripQuerySubmissionTimeAndClientUploadDurationParam(
                lens::BuildTextOnlySearchURL(
                    kTestTime, text_query,
                    /*page_url=*/std::nullopt,
                    /*page_title=*/std::nullopt, additional_params,
                    lens::LensOverlayInvocationSource::kAppMenu,
                    lens::LensOverlaySelectionType::UNKNOWN_SELECTION_TYPE,
                    /*use_dark_mode=*/false)),
            expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildTextOnlySearchURLPunctuation) {
  std::string text_query = "Red Apples!?#";
  std::map<std::string, std::string> additional_params;
  std::string escaped_text_query =
      base::EscapeQueryParamValue(text_query, /*use_plus=*/true);
  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&q=%s&cs=0&gsc=2&hl=%s", kResultsSearchBaseUrl,
      escaped_text_query.c_str(), kLanguage);

  EXPECT_EQ(StripQuerySubmissionTimeAndClientUploadDurationParam(
                lens::BuildTextOnlySearchURL(
                    kTestTime, text_query,
                    /*page_url=*/std::nullopt,
                    /*page_title=*/std::nullopt, additional_params,
                    lens::LensOverlayInvocationSource::kAppMenu,
                    lens::LensOverlaySelectionType::UNKNOWN_SELECTION_TYPE,
                    /*use_dark_mode=*/false)),
            expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildTextOnlySearchURLWhitespace) {
  std::string text_query = "Red Apples";
  std::map<std::string, std::string> additional_params;
  std::string escaped_text_query =
      base::EscapeQueryParamValue(text_query, /*use_plus=*/true);
  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&q=%s&cs=0&gsc=2&hl=%s", kResultsSearchBaseUrl,
      escaped_text_query.c_str(), kLanguage);

  EXPECT_EQ(StripQuerySubmissionTimeAndClientUploadDurationParam(
                lens::BuildTextOnlySearchURL(
                    kTestTime, text_query,
                    /*page_url=*/std::nullopt,
                    /*page_title=*/std::nullopt, additional_params,
                    lens::LensOverlayInvocationSource::kAppMenu,
                    lens::LensOverlaySelectionType::UNKNOWN_SELECTION_TYPE,
                    /*use_dark_mode=*/false)),
            expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildLensSearchURLEmptyClusterInfo) {
  std::string text_query = "Green Apples";
  std::map<std::string, std::string> additional_params;
  std::string escaped_text_query =
      base::EscapeQueryParamValue(text_query, /*use_plus=*/true);
  uint64_t uuid = 12345;
  int sequence_id = 1;
  int image_sequence_id = 3;

  lens::LensOverlayClusterInfo cluster_info;
  std::unique_ptr<lens::LensOverlayRequestId> request_id =
      std::make_unique<lens::LensOverlayRequestId>();
  request_id->set_uuid(uuid);
  request_id->set_sequence_id(sequence_id);
  request_id->set_image_sequence_id(image_sequence_id);

  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&cs=0&gsc=2&hl=%s&q=%s&lns_mode=mu&"
      "lns_fp=1&lns_surface=42&gsessionid=&udm=24&vsrid=%s",
      kResultsSearchBaseUrl, kLanguage, escaped_text_query.c_str(),
      EncodeRequestId(request_id.get()).c_str());

  EXPECT_EQ(
      StripQuerySubmissionTimeAndClientUploadDurationParam(
          lens::BuildLensSearchURL(
              kTestTime, text_query, /*page_url=*/std::nullopt,
              /*page_title=*/std::nullopt, std::move(request_id), cluster_info,
              additional_params, lens::LensOverlayInvocationSource::kAppMenu,
              /*use_dark_mode=*/false)),
      expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildLensSearchURLWithSessionId) {
  std::string text_query = "Green Apples";
  std::map<std::string, std::string> additional_params;
  std::string escaped_text_query =
      base::EscapeQueryParamValue(text_query, /*use_plus=*/true);
  uint64_t uuid = 12345;
  int sequence_id = 1;
  int image_sequence_id = 3;
  std::string search_session_id = "search_session_id";

  std::unique_ptr<lens::LensOverlayRequestId> request_id =
      std::make_unique<lens::LensOverlayRequestId>();
  lens::LensOverlayClusterInfo cluster_info;
  cluster_info.set_search_session_id(search_session_id);
  request_id->set_uuid(uuid);
  request_id->set_sequence_id(sequence_id);
  request_id->set_image_sequence_id(image_sequence_id);

  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&cs=0&gsc=2&hl=%s&q=%s&lns_mode=mu&"
      "lns_fp=1&lns_surface=42&gsessionid=%s&udm=24&vsrid=%s",
      kResultsSearchBaseUrl, kLanguage, escaped_text_query.c_str(),
      search_session_id.c_str(), EncodeRequestId(request_id.get()).c_str());

  EXPECT_EQ(
      StripQuerySubmissionTimeAndClientUploadDurationParam(
          lens::BuildLensSearchURL(
              kTestTime, text_query, /*page_url=*/std::nullopt,
              /*page_title=*/std::nullopt, std::move(request_id), cluster_info,
              additional_params, lens::LensOverlayInvocationSource::kAppMenu,
              /*use_dark_mode=*/false)),
      expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildLensSearchURLWithNoTextQuery) {
  std::map<std::string, std::string> additional_params;
  uint64_t uuid = 12345;
  int sequence_id = 1;
  int image_sequence_id = 3;
  std::string search_session_id = "search_session_id";

  std::unique_ptr<lens::LensOverlayRequestId> request_id =
      std::make_unique<lens::LensOverlayRequestId>();
  lens::LensOverlayClusterInfo cluster_info;
  cluster_info.set_search_session_id(search_session_id);
  request_id->set_uuid(uuid);
  request_id->set_sequence_id(sequence_id);
  request_id->set_image_sequence_id(image_sequence_id);

  std::string serialized_request_id;
  CHECK(request_id.get()->SerializeToString(&serialized_request_id));
  std::string encoded_request_id;
  base::Base64UrlEncode(serialized_request_id,
                        base::Base64UrlEncodePolicy::OMIT_PADDING,
                        &encoded_request_id);

  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&cs=0&gsc=2&hl=%s&q=&lns_mode=un&"
      "lns_fp=1&lns_surface=42&gsessionid=%s&udm=26&vsrid=%s",
      kResultsSearchBaseUrl, kLanguage, search_session_id.c_str(),
      encoded_request_id.c_str());

  EXPECT_EQ(
      StripQuerySubmissionTimeAndClientUploadDurationParam(
          lens::BuildLensSearchURL(
              kTestTime,
              /*text_query=*/std::nullopt, /*page_url=*/std::nullopt,
              /*page_title=*/std::nullopt, std::move(request_id), cluster_info,
              additional_params, lens::LensOverlayInvocationSource::kAppMenu,
              /*use_dark_mode=*/false)),
      expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, BuildLensSearchURLWithAdditionalParams) {
  std::map<std::string, std::string> additional_params = {{"param", "value"}};
  uint64_t uuid = 12345;
  int sequence_id = 1;
  int image_sequence_id = 3;
  std::string search_session_id = "search_session_id";

  std::unique_ptr<lens::LensOverlayRequestId> request_id =
      std::make_unique<lens::LensOverlayRequestId>();
  lens::LensOverlayClusterInfo cluster_info;
  cluster_info.set_search_session_id(search_session_id);
  request_id->set_uuid(uuid);
  request_id->set_sequence_id(sequence_id);
  request_id->set_image_sequence_id(image_sequence_id);

  std::string serialized_request_id;
  CHECK(request_id.get()->SerializeToString(&serialized_request_id));
  std::string encoded_request_id;
  base::Base64UrlEncode(serialized_request_id,
                        base::Base64UrlEncodePolicy::OMIT_PADDING,
                        &encoded_request_id);

  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&param=value&cs=0&gsc=2&hl=%s&q=&lns_"
      "mode=un&lns_fp=1&lns_surface=42&gsessionid=%s&udm=26&"
      "vsrid=%s",
      kResultsSearchBaseUrl, kLanguage, search_session_id.c_str(),
      encoded_request_id.c_str());

  EXPECT_TRUE(lens::AreSearchUrlsEquivalent(
      StripQuerySubmissionTimeAndClientUploadDurationParam(
          lens::BuildLensSearchURL(
              kTestTime,
              /*text_query=*/std::nullopt, /*page_url=*/std::nullopt,
              /*page_title=*/std::nullopt, std::move(request_id), cluster_info,
              additional_params, lens::LensOverlayInvocationSource::kAppMenu,
              /*use_dark_mode=*/false)),
      GURL(expected_url)));
}

TEST_F(LensOverlayUrlBuilderTest, BuildMultimodalSearchURLWithVideoContext) {
  std::string text_query = "Green Apples";
  std::map<std::string, std::string> additional_params;
  std::string escaped_text_query =
      base::EscapeQueryParamValue(text_query, /*use_plus=*/true);
  uint64_t uuid = 12345;
  int sequence_id = 1;
  int image_sequence_id = 3;
  std::string search_session_id = "search_session_id";

  std::unique_ptr<lens::LensOverlayRequestId> request_id =
      std::make_unique<lens::LensOverlayRequestId>();
  lens::LensOverlayClusterInfo cluster_info;
  cluster_info.set_search_session_id(search_session_id);
  request_id->set_uuid(uuid);
  request_id->set_sequence_id(sequence_id);
  request_id->set_image_sequence_id(image_sequence_id);

  std::string serialized_request_id;
  CHECK(request_id.get()->SerializeToString(&serialized_request_id));
  std::string encoded_request_id;
  base::Base64UrlEncode(serialized_request_id,
                        base::Base64UrlEncodePolicy::OMIT_PADDING,
                        &encoded_request_id);

  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&cs=0&gsc=2&hl=%s&q=%s&lns_"
      "mode=mu&lns_fp=1&lns_surface=42&gsessionid=%s&udm=24&"
      "vsrid=%s",
      kResultsSearchBaseUrl, kLanguage, escaped_text_query.c_str(),
      search_session_id.c_str(), encoded_request_id.c_str());

  EXPECT_EQ(StripQuerySubmissionTimeAndClientUploadDurationParam(
                lens::BuildLensSearchURL(
                    kTestTime, text_query, std::make_optional<GURL>(kPageUrl),
                    std::make_optional<std::string>(kPageTitle),
                    std::move(request_id), cluster_info, additional_params,
                    lens::LensOverlayInvocationSource::kAppMenu,
                    /*use_dark_mode=*/false)),
            expected_url);
}
TEST_F(LensOverlayUrlBuilderTest,
       BuildImageOnlySearchURLWithVideoContextDoesNotAttachContext) {
  std::map<std::string, std::string> additional_params;
  uint64_t uuid = 12345;
  int sequence_id = 1;
  int image_sequence_id = 3;
  std::string search_session_id = "search_session_id";

  std::unique_ptr<lens::LensOverlayRequestId> request_id =
      std::make_unique<lens::LensOverlayRequestId>();
  lens::LensOverlayClusterInfo cluster_info;
  cluster_info.set_search_session_id(search_session_id);
  request_id->set_uuid(uuid);
  request_id->set_sequence_id(sequence_id);
  request_id->set_image_sequence_id(image_sequence_id);

  std::string serialized_request_id;
  CHECK(request_id.get()->SerializeToString(&serialized_request_id));
  std::string encoded_request_id;
  base::Base64UrlEncode(serialized_request_id,
                        base::Base64UrlEncodePolicy::OMIT_PADDING,
                        &encoded_request_id);

  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&cs=0&gsc=2&hl=%s&q=&lns_"
      "mode=un&lns_fp=1&lns_surface=42&gsessionid=%s&udm=26&"
      "vsrid=%s",
      kResultsSearchBaseUrl, kLanguage, search_session_id.c_str(),
      encoded_request_id.c_str());

  EXPECT_EQ(
      StripQuerySubmissionTimeAndClientUploadDurationParam(
          lens::BuildLensSearchURL(
              kTestTime,
              /*text_query=*/std::nullopt, std::make_optional<GURL>(kPageUrl),
              std::make_optional<std::string>(kPageTitle),
              std::move(request_id), cluster_info, additional_params,
              lens::LensOverlayInvocationSource::kAppMenu,
              /*use_dark_mode=*/false)),
      expected_url);
}

TEST_F(LensOverlayUrlBuilderTest,
       BuildMultimodalSearchURLWithVideoContextFlagOff) {
  feature_list_.Reset();
  feature_list_.InitWithFeaturesAndParameters(
      {{lens::features::kLensOverlay,
        {
            {"results-search-url", kResultsSearchBaseUrl},
        }},
       {lens::features::kLensOverlayContextualSearchbox,
        {
            {"use-video-context-for-multimodal-requests", "false"},
        }},
       {lens::features::kLensOverlayUpdatedClientContext, {}}},
      /*disabled_features=*/{});

  std::string text_query = "Green Apples";
  std::map<std::string, std::string> additional_params;
  std::string escaped_text_query =
      base::EscapeQueryParamValue(text_query, /*use_plus=*/true);
  uint64_t uuid = 12345;
  int sequence_id = 1;
  int image_sequence_id = 3;
  std::string search_session_id = "search_session_id";

  std::unique_ptr<lens::LensOverlayRequestId> request_id =
      std::make_unique<lens::LensOverlayRequestId>();
  lens::LensOverlayClusterInfo cluster_info;
  cluster_info.set_search_session_id(search_session_id);
  request_id->set_uuid(uuid);
  request_id->set_sequence_id(sequence_id);
  request_id->set_image_sequence_id(image_sequence_id);

  std::string serialized_request_id;
  CHECK(request_id.get()->SerializeToString(&serialized_request_id));
  std::string encoded_request_id;
  base::Base64UrlEncode(serialized_request_id,
                        base::Base64UrlEncodePolicy::OMIT_PADDING,
                        &encoded_request_id);

  std::string expected_url = base::StringPrintf(
      "%s?source=chrome.cr.menu&cs=0&gsc=2&hl=%s&q=%s&lns_"
      "mode=mu&lns_fp=1&lns_surface=42&gsessionid=%s&udm=24&"
      "vsrid=%s",
      kResultsSearchBaseUrl, kLanguage, escaped_text_query.c_str(),
      search_session_id.c_str(), encoded_request_id.c_str());

  EXPECT_EQ(StripQuerySubmissionTimeAndClientUploadDurationParam(
                lens::BuildLensSearchURL(
                    kTestTime, text_query, std::make_optional<GURL>(kPageUrl),
                    std::make_optional<std::string>(kPageTitle),
                    std::move(request_id), cluster_info, additional_params,
                    lens::LensOverlayInvocationSource::kAppMenu,
                    /*use_dark_mode=*/false)),
            expected_url);
}

TEST_F(LensOverlayUrlBuilderTest, HasCommonSearchQueryParameters) {
  const GURL url(base::StringPrintf("%s?gsc=2&hl=%s&cs=1",
                                    kResultsSearchBaseUrl, kLanguage));
  EXPECT_TRUE(lens::HasCommonSearchQueryParameters(url));
}

TEST_F(LensOverlayUrlBuilderTest, HasCommonSearchQueryParametersWithoutLocale) {
  const GURL url(base::StringPrintf("%s?gsc=2", kResultsSearchBaseUrl));
  EXPECT_FALSE(lens::HasCommonSearchQueryParameters(url));
}

TEST_F(LensOverlayUrlBuilderTest,
       HasCommonSearchQueryParametersMissingQueryParams) {
  const GURL failing_url1(
      base::StringPrintf("%s?gsc=2", kResultsSearchBaseUrl));
  EXPECT_FALSE(lens::HasCommonSearchQueryParameters(failing_url1));

  const GURL failing_url2(
      base::StringPrintf("%s?hl=%s", kResultsSearchBaseUrl, kLanguage));
  EXPECT_FALSE(lens::HasCommonSearchQueryParameters(failing_url2));

  const GURL failing_url3(base::StringPrintf("%s?cs=1", kResultsSearchBaseUrl));
  EXPECT_FALSE(lens::HasCommonSearchQueryParameters(failing_url3));
}

TEST_F(LensOverlayUrlBuilderTest,
       AppendDarkModeParamToURLToUrlAppendsDarkMode) {
  const GURL base_url(kResultsSearchBaseUrl);

  std::string expected_light_mode_url =
      base::StringPrintf("%s?cs=0", kResultsSearchBaseUrl);
  EXPECT_EQ(lens::AppendDarkModeParamToURL(base_url, /*use_dark_mode=*/false),
            expected_light_mode_url);

  std::string expected_dark_mode_url =
      base::StringPrintf("%s?cs=1", kResultsSearchBaseUrl);
  EXPECT_EQ(lens::AppendDarkModeParamToURL(base_url, /*use_dark_mode=*/true),
            expected_dark_mode_url);
}

TEST_F(LensOverlayUrlBuilderTest, HasCommonSearchQueryParametersNoQueryParams) {
  const GURL url(kResultsSearchBaseUrl);
  EXPECT_FALSE(lens::HasCommonSearchQueryParameters(url));
}

TEST_F(LensOverlayUrlBuilderTest, IsValidSearchURL) {
  EXPECT_TRUE(lens::IsValidSearchResultsUrl(GURL(kResultsSearchBaseUrl)));
}

TEST_F(LensOverlayUrlBuilderTest, IsValidSearchURLDifferentDomains) {
  EXPECT_FALSE(
      lens::IsValidSearchResultsUrl(GURL("https://test.google/search")));
}

TEST_F(LensOverlayUrlBuilderTest, IsValidSearchURLDifferentSchemes) {
  // GetContent() should return everything after the scheme.
  GURL different_scheme_url = GURL(base::StringPrintf(
      "chrome://%s", GURL(kResultsSearchBaseUrl).GetContent().c_str()));
  EXPECT_FALSE(lens::IsValidSearchResultsUrl(different_scheme_url));
}

TEST_F(LensOverlayUrlBuilderTest, IsValidSearchURLDifferentPaths) {
  EXPECT_FALSE(lens::IsValidSearchResultsUrl(
      GURL(kResultsSearchBaseUrl).GetWithEmptyPath()));
}

TEST_F(LensOverlayUrlBuilderTest, IsValidSearchURLInvalidURL) {
  EXPECT_FALSE(lens::IsValidSearchResultsUrl(GURL()));
}

TEST_F(LensOverlayUrlBuilderTest, RemoveIgnoredSearchURLParameters) {
  std::string text_query = "Apples";
  std::string viewport_width = "400";
  std::string viewport_height = "500";
  std::string initial_url = base::StringPrintf(
      "%s?q=%s&gsc=2&hl=%s&biw=%s&bih=%s&sec_act=1&sxsrf=token",
      kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
      viewport_width.c_str(), viewport_height.c_str());
  std::string expected_url =
      base::StringPrintf("%s?q=%s&gsc=2&hl=%s", kResultsSearchBaseUrl,
                         text_query.c_str(), kLanguage);

  EXPECT_EQ(lens::RemoveIgnoredSearchURLParameters(GURL(initial_url)),
            GURL(expected_url));
}

TEST_F(LensOverlayUrlBuilderTest, RemoveSidePanelURLParameters) {
  std::string text_query = "Apples";
  std::string viewport_width = "400";
  std::string viewport_height = "500";
  std::string initial_url = base::StringPrintf(
      "%s?q=%s&gsc=2&hl=%s&biw=%s&bih=%s&sec_act=1&sxsrf=token",
      kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
      viewport_width.c_str(), viewport_height.c_str());
  std::string expected_url =
      base::StringPrintf("%s?q=%s&hl=%s&biw=%s&bih=%s&sec_act=1&sxsrf=token",
                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
                         viewport_width.c_str(), viewport_height.c_str());

  EXPECT_EQ(lens::RemoveSidePanelURLParameters(GURL(initial_url)),
            GURL(expected_url));
}

TEST_F(LensOverlayUrlBuilderTest, GetSearchResultsUrlFromRedirectUrl) {
  std::string text_query = "Apples";
  std::string viewport_width = "400";
  std::string viewport_height = "500";
  std::string relative_search_url = base::StringPrintf(
      "/search?q=%s&gsc=2&hl=%s&biw=%s&bih=%s", text_query.c_str(), kLanguage,
      viewport_width.c_str(), viewport_height.c_str());
  std::string escaped_relative_search_url =
      base::EscapeUrlEncodedData(relative_search_url, /*use_plus=*/false);
  GURL search_url = GURL(kResultsSearchBaseUrl).Resolve(relative_search_url);

  std::string initial_url =
      base::StringPrintf("%s?url=%s", kResultsRedirectBaseUrl,
                         escaped_relative_search_url.c_str());
  EXPECT_EQ(lens::GetSearchResultsUrlFromRedirectUrl(GURL(initial_url)),
            GURL(search_url));
}

TEST_F(LensOverlayUrlBuilderTest, IsLensTextSelectionType) {
  EXPECT_TRUE(IsLensTextSelectionType(lens::SELECT_TEXT_HIGHLIGHT));
  EXPECT_TRUE(IsLensTextSelectionType(lens::SELECT_TRANSLATED_TEXT));
  EXPECT_TRUE(IsLensTextSelectionType(lens::TRANSLATE_CHIP));
  EXPECT_FALSE(IsLensTextSelectionType(lens::REGION_SEARCH));
  EXPECT_FALSE(IsLensTextSelectionType(lens::TAP_ON_OBJECT));
  EXPECT_FALSE(IsLensTextSelectionType(lens::MULTIMODAL_SEARCH));
}

TEST_F(LensOverlayUrlBuilderTest, BuildTranslateLanguagesURL) {
  GURL endpoint_url = GURL(features::GetLensOverlayTranslateEndpointURL());

  GURL translate_url_english_us = BuildTranslateLanguagesURL("US", "en");
  GURL translate_url_english_uk = BuildTranslateLanguagesURL("UK", "en");
  GURL translate_url_french_nocountry = BuildTranslateLanguagesURL("", "fr");

  EXPECT_EQ(translate_url_english_us.GetWithEmptyPath(),
            endpoint_url.GetWithEmptyPath());
  EXPECT_EQ(translate_url_english_uk.GetWithEmptyPath(),
            endpoint_url.GetWithEmptyPath());
  EXPECT_EQ(translate_url_french_nocountry.GetWithEmptyPath(),
            endpoint_url.GetWithEmptyPath());

  std::string country_param_value;
  net::GetValueForKeyInQuery(translate_url_english_us, kCountryQueryParameter,
                             &country_param_value);
  EXPECT_EQ(country_param_value, "US");
  net::GetValueForKeyInQuery(translate_url_english_uk, kCountryQueryParameter,
                             &country_param_value);
  EXPECT_EQ(country_param_value, "UK");
  net::GetValueForKeyInQuery(translate_url_french_nocountry,
                             kCountryQueryParameter, &country_param_value);
  EXPECT_EQ(country_param_value, "");

  std::string lang_param_value;
  net::GetValueForKeyInQuery(translate_url_english_us,
                             kDisplayLanguageQueryParameter, &lang_param_value);
  EXPECT_EQ(lang_param_value, "en");
  net::GetValueForKeyInQuery(translate_url_french_nocountry,
                             kDisplayLanguageQueryParameter, &lang_param_value);
  EXPECT_EQ(lang_param_value, "fr");
  net::GetValueForKeyInQuery(translate_url_english_uk,
                             kDisplayLanguageQueryParameter, &lang_param_value);
  EXPECT_EQ(lang_param_value, "en");

  std::string client_param_value;
  net::GetValueForKeyInQuery(translate_url_english_us, kClientIdQueryParameter,
                             &client_param_value);
  EXPECT_EQ(client_param_value, "lens-overlay");
  client_param_value = "";
  net::GetValueForKeyInQuery(translate_url_french_nocountry,
                             kClientIdQueryParameter, &client_param_value);
  EXPECT_EQ(client_param_value, "lens-overlay");
  client_param_value = "";
  net::GetValueForKeyInQuery(translate_url_english_uk, kClientIdQueryParameter,
                             &client_param_value);
  EXPECT_EQ(client_param_value, "lens-overlay");
}

TEST_F(LensOverlayUrlBuilderTest, ShouldOpenSearchURLInNewTab) {
  const GURL base_results_url = GURL(kResultsSearchBaseUrl);
  const GURL results_url_shopping_mode =
      GURL(std::string(kResultsSearchBaseUrl) + "?udm=28");
  const GURL results_url_unimodal =
      GURL(std::string(kResultsSearchBaseUrl) + "?udm=26");
  const GURL results_url_multimodal =
      GURL(std::string(kResultsSearchBaseUrl) + "?udm=24");
  const GURL results_url_aim_mode =
      GURL(std::string(kResultsSearchBaseUrl) + "?udm=50");
  EXPECT_FALSE(lens::ShouldOpenSearchURLInNewTab(
      base_results_url, /*is_aim_feature_enabled=*/false));
  EXPECT_FALSE(lens::ShouldOpenSearchURLInNewTab(
      results_url_unimodal, /*is_aim_feature_enabled=*/false));
  EXPECT_FALSE(lens::ShouldOpenSearchURLInNewTab(
      results_url_multimodal, /*is_aim_feature_enabled=*/false));
  EXPECT_TRUE(lens::ShouldOpenSearchURLInNewTab(
      results_url_shopping_mode, /*is_aim_feature_enabled=*/false));
  EXPECT_TRUE(lens::ShouldOpenSearchURLInNewTab(
      results_url_aim_mode, /*is_aim_feature_enabled=*/false));
  EXPECT_FALSE(lens::ShouldOpenSearchURLInNewTab(
      results_url_aim_mode, /*is_aim_feature_enabled=*/true));
}

TEST_F(LensOverlayUrlBuilderTest, MaybeStripParamsForShopping) {
  const GURL non_shopping_url =
      GURL(std::string(kResultsSearchBaseUrl) + "?q=query&stick=abc");
  const GURL shopping_url =
      GURL(std::string(kResultsSearchBaseUrl) + "?q=query&udm=28&stick=abc");
  const GURL expected_shopping_url =
      GURL(std::string(kResultsSearchBaseUrl) + "?q=query&udm=28");
  const GURL external_shopping_url =
      GURL("https://example.com/search?q=query&udm=28&stick=abc");

  // Non-shopping search results URL should not be modified.
  EXPECT_EQ(lens::MaybeStripParamsForShopping(non_shopping_url),
            non_shopping_url);

  // Shopping search results URL should have 'stick' parameter stripped.
  EXPECT_EQ(lens::MaybeStripParamsForShopping(shopping_url),
            expected_shopping_url);

  // Non-search URL with shopping params should not be modified.
  EXPECT_EQ(lens::MaybeStripParamsForShopping(external_shopping_url),
            external_shopping_url);
}

TEST_F(LensOverlayUrlBuilderTest, URLsMatchWithoutTextFragment) {
  // Text fragments do not match.
  EXPECT_TRUE(URLsMatchWithoutTextFragment(
      GURL("https://www.google.com/path?q=text#ref"),
      GURL("https://www.google.com/path?q=text#ref:~:text=apples")));
  EXPECT_TRUE(URLsMatchWithoutTextFragment(
      GURL("https://www.google.com/path?q=text#ref:~:text=oranges"),
      GURL("https://www.google.com/path?q=text#ref")));
  EXPECT_TRUE(URLsMatchWithoutTextFragment(
      GURL("https://www.google.com/path?q=text#ref:~:text=oranges"),
      GURL("https://www.google.com/path?q=text#ref:~:text=apples")));

  // Refs without text fragments do not match.
  EXPECT_FALSE(URLsMatchWithoutTextFragment(
      GURL("https://www.google.com/path?q=text#section1"),
      GURL("https://www.google.com/path?q=text#section2")));
  EXPECT_FALSE(URLsMatchWithoutTextFragment(
      GURL("https://www.google.com/path?q=text#section1:~:text=oranges"),
      GURL("https://www.google.com/path?q=text#section2:~:text=apples")));

  // Query param does not match.
  EXPECT_FALSE(URLsMatchWithoutTextFragment(
      GURL("https://www.google.com/path?q=text#ref"),
      GURL("https://www.google.com/path?q=query#ref")));

  // Path does not match.
  EXPECT_FALSE(URLsMatchWithoutTextFragment(
      GURL("https://www.google.com/search?q=text#ref"),
      GURL("https://www.google.com/path?q=text#ref")));

  // Host does not match.
  EXPECT_FALSE(URLsMatchWithoutTextFragment(
      GURL("https://www.google.com/path?q=text#ref"),
      GURL("https://www.example.com/path?q=text#ref")));

  // Scheme does not match.
  EXPECT_FALSE(URLsMatchWithoutTextFragment(
      GURL("https://www.google.com/path?q=text#ref"),
      GURL("http://www.google.com/path?q=text#ref")));
}

TEST_F(LensOverlayUrlBuilderTest, AddPDFScrollToParametersToUrl) {
  const GURL base_url = GURL(
      "https://www.example.com/"
      "example.pdf?query=example#anycurrenthash:~:text=example");

  const int expected_pdf_page_number = 19;
  const std::vector<std::string> expected_text_fragments = {"apples", "oranges",
                                                            "pineapples"};
  const GURL actual_url = AddPDFScrollToParametersToUrl(
      base_url, expected_text_fragments, expected_pdf_page_number);
  EXPECT_TRUE(base_url.EqualsIgnoringRef(actual_url));
  EXPECT_EQ(actual_url.GetRef(),
            base::StringPrintf("page=%d:~:text=%s&text=%s&text=%s",
                               expected_pdf_page_number, "apples", "oranges",
                               "pineapples"));

  const std::vector<std::string> expected_one_fragment = {"apples"};
  const GURL actual_url_one_fragment = AddPDFScrollToParametersToUrl(
      base_url, expected_one_fragment, expected_pdf_page_number);
  EXPECT_TRUE(base_url.EqualsIgnoringRef(actual_url_one_fragment));
  EXPECT_EQ(actual_url_one_fragment.GetRef(),
            base::StringPrintf("page=%d:~:text=%s", expected_pdf_page_number,
                               "apples"));

  const GURL actual_url_no_fragments =
      AddPDFScrollToParametersToUrl(base_url, {}, expected_pdf_page_number);
  EXPECT_TRUE(base_url.EqualsIgnoringRef(actual_url_no_fragments));
  EXPECT_EQ(actual_url_no_fragments.GetRef(),
            base::StringPrintf("page=%d", expected_pdf_page_number));
}

TEST_F(LensOverlayUrlBuilderTest, ExtractTimeInSecondsFromQueryIfExists) {
  EXPECT_EQ(lens::ExtractTimeInSecondsFromQueryIfExists(
                GURL("https://www.youtube.com/watch?v=test&t=10s")),
            base::Seconds(10));
  EXPECT_EQ(lens::ExtractTimeInSecondsFromQueryIfExists(
                GURL("https://www.youtube.com/watch?v=test&t=10")),
            base::Seconds(10));
  EXPECT_EQ(lens::ExtractTimeInSecondsFromQueryIfExists(
                GURL("https://www.youtube.com/watch?v=test&t=1s")),
            base::Seconds(1));
  EXPECT_EQ(lens::ExtractTimeInSecondsFromQueryIfExists(
                GURL("https://www.youtube.com/watch?v=test&t=1")),
            base::Seconds(1));
  EXPECT_EQ(lens::ExtractTimeInSecondsFromQueryIfExists(
                GURL("https://www.youtube.com/watch?v=test&t=60s")),
            base::Seconds(60));
  EXPECT_EQ(lens::ExtractTimeInSecondsFromQueryIfExists(
                GURL("https://www.youtube.com/watch?v=test")),
            std::nullopt);
  EXPECT_EQ(lens::ExtractTimeInSecondsFromQueryIfExists(
                GURL("https://www.youtube.com/watch?v=test&t=s")),
            std::nullopt);
  EXPECT_EQ(lens::ExtractTimeInSecondsFromQueryIfExists(
                GURL("https://www.youtube.com/watch?v=test&t=abcs")),
            std::nullopt);
}

TEST_F(LensOverlayUrlBuilderTest, ExtractVideoNameIfExists) {
  EXPECT_EQ(lens::ExtractVideoNameIfExists(
                GURL("https://www.youtube.com/watch?v=VIDEO_ID")),
            "VIDEO_ID");
  EXPECT_EQ(lens::ExtractVideoNameIfExists(
                GURL("https://www.youtube.com/embed/VIDEO_ID")),
            "VIDEO_ID");
  EXPECT_EQ(lens::ExtractVideoNameIfExists(GURL("https://www.google.com")),
            std::nullopt);
  EXPECT_EQ(
      lens::ExtractVideoNameIfExists(GURL("https://www.google.com?v=VIDEO_ID")),
      std::nullopt);
  EXPECT_EQ(lens::ExtractVideoNameIfExists(
                GURL("https://www.google.com/embed/VIDEO_ID")),
            std::nullopt);
  EXPECT_EQ(lens::ExtractVideoNameIfExists(
                GURL("https://www.google.com/watch?v=VIDEO_ID")),
            std::nullopt);
  EXPECT_EQ(
      lens::ExtractVideoNameIfExists(GURL("https://www.youtube.com/watch?v=")),
      std::nullopt);
  EXPECT_EQ(
      lens::ExtractVideoNameIfExists(GURL("https://www.youtube.com/embed/")),
      std::nullopt);
}

}  // namespace lens
