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

#include <string>
#include <string_view>

#include "base/base64url.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/autofill/autofill_uitest_util.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/core/browser/data_manager/personal_data_manager.h"
#include "components/autofill/core/browser/data_manager/personal_data_manager_observer.h"
#include "components/autofill/core/browser/data_model/addresses/autofill_profile.h"
#include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
#include "components/autofill/core/common/autofill_debug_features.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "net/dns/mock_host_resolver.h"
#include "services/network/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/switches.h"

using testing::AllOf;
using testing::Eq;
using testing::Matcher;
using testing::Property;
using version_info::GetProductNameAndVersionForUserAgent;

namespace autofill {
namespace {

class WindowedNetworkObserver {
 public:
  explicit WindowedNetworkObserver(Matcher<std::string> expected_upload_data)
      : expected_upload_data_(expected_upload_data),
        message_loop_runner_(new content::MessageLoopRunner) {
    interceptor_ =
        std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
            &WindowedNetworkObserver::OnIntercept, base::Unretained(this)));
  }

  WindowedNetworkObserver(const WindowedNetworkObserver&) = delete;
  WindowedNetworkObserver& operator=(const WindowedNetworkObserver&) = delete;

  ~WindowedNetworkObserver() = default;

  // Waits for a network request with the `expected_upload_data_`.
  void Wait() {
    message_loop_runner_->Run();
    interceptor_.reset();
  }

 private:
  // Helper to extract the value passed to a lookup in the URL. Returns "*** not
  // found ***" if the the data cannot be decoded.
  std::string GetLookupContent(const std::string& query_path) {
    if (query_path.find("/v1/pages/") == std::string::npos)
      return "*** not found ***";
    std::string payload = query_path.substr(strlen("/v1/pages/"));
    std::string decoded_payload;
    if (base::Base64UrlDecode(payload,
                              base::Base64UrlDecodePolicy::REQUIRE_PADDING,
                              &decoded_payload)) {
      return decoded_payload;
    }
    return "*** not found ***";
  }

  bool OnIntercept(content::URLLoaderInterceptor::RequestParams* params) {
    // NOTE: This constant matches the one defined in
    // components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager.cc
    static const char kDefaultAutofillServerURL[] =
        "https://content-autofill.googleapis.com/";
    DCHECK(params);
    const network::ResourceRequest& resource_request = params->url_request;
    if (resource_request.url.spec().find(kDefaultAutofillServerURL) ==
        std::string::npos) {
      return false;
    }

    const std::string& data =
        (resource_request.method == "GET")
            ? GetLookupContent(resource_request.url.GetPath())
            : network::GetUploadData(resource_request);

    if (expected_upload_data_.Matches(data))
      message_loop_runner_->Quit();

    return false;
  }

 private:
  Matcher<std::string> expected_upload_data_;
  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;

  std::unique_ptr<content::URLLoaderInterceptor> interceptor_;
};

class AutofillServerTest : public InProcessBrowserTest {
 public:
  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    // Wait for Personal Data Manager to be fully loaded as the events about
    // being loaded may throw off the tests and cause flakiness.
    WaitForPersonalDataManagerToBeLoaded(browser()->GetProfile());

    // Set up the HTTPS (!) server (embedded_test_server() is an HTTP server).
    // Every hostname is handled by that server.
    host_resolver()->AddRule("a.com", "127.0.0.1");
    cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
    embedded_https_test_server().SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
    embedded_https_test_server().RegisterRequestHandler(base::BindRepeating(
        [](const std::map<std::string, std::string>* pages,
           const net::test_server::HttpRequest& request)
            -> std::unique_ptr<net::test_server::HttpResponse> {
          auto it = pages->find(request.GetURL().GetPath());
          if (it == pages->end()) {
            return nullptr;
          }
          auto response =
              std::make_unique<net::test_server::BasicHttpResponse>();
          response->set_code(net::HTTP_OK);
          response->set_content_type("text/html;charset=utf-8");
          response->set_content(it->second);
          return response;
        },
        base::Unretained(&pages_)));
    ASSERT_TRUE(embedded_https_test_server().InitializeAndListen());
    embedded_https_test_server().StartAcceptingConnections();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    cert_verifier_.SetUpCommandLine(command_line);
    // Slower test bots (ChromeOS, debug, etc.) are flaky due to slower loading
    // interacting with deferred commits.
    command_line->AppendSwitch(blink::switches::kAllowPreCommitInput);
  }

  void NavigateToUrl(std::string_view relative_url) {
    NavigateParams params(
        browser(), embedded_https_test_server().GetURL("a.com", relative_url),
        ui::PAGE_TRANSITION_LINK);
    params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
    ui_test_utils::NavigateToURL(&params);
  }

  // Registers the response `content_html` for a given `relative_path`.
  void SetUrlContent(std::string relative_path, std::string_view content_html) {
    ASSERT_EQ(relative_path[0], '/');
    pages_[std::move(relative_path)] = content_html;
  }

 private:
  test::AutofillBrowserTestEnvironment autofill_test_environment_;
  base::test::ScopedFeatureList scoped_feature_list_{
      features::debug::kAutofillServerCommunication};
  content::ContentMockCertVerifier cert_verifier_;
  std::map<std::string, std::string> pages_;
};

MATCHER_P(EqualsUploadProto, expected_const, "") {
  AutofillUploadRequest expected = expected_const;
  AutofillUploadRequest request;
  if (!request.ParseFromString(arg))
    return false;

  // Remove metadata because it is randomized and won't match.
  EXPECT_EQ(request.upload().has_randomized_form_metadata(),
            expected.upload().has_randomized_form_metadata());
  request.mutable_upload()->clear_randomized_form_metadata();
  expected.mutable_upload()->clear_randomized_form_metadata();
  EXPECT_EQ(request.upload().field_data_size(),
            expected.upload().field_data_size());
  if (request.upload().field_data_size() !=
      expected.upload().field_data_size()) {
    return false;

  }
  for (int i = 0; i < request.upload().field_data_size(); i++) {
    request.mutable_upload()
        ->mutable_field_data(i)
        ->clear_randomized_field_metadata();
    expected.mutable_upload()
        ->mutable_field_data(i)
        ->clear_randomized_field_metadata();
  }

  // TODO(crbug.com/40792292): The language is sometimes missing from the
  // upload, making the test flaky. Add the language back to the comparison when
  // the root cause is fixed.
  request.mutable_upload()->clear_language();
  expected.mutable_upload()->clear_language();

  return request.SerializeAsString() == expected.SerializeAsString();
}

MATCHER_P(EqualsQueryProto, expected_const, "") {
  AutofillPageQueryRequest expected = expected_const;
  AutofillPageQueryRequest request;
  if (!request.ParseFromString(arg)) {
    return false;
  }

  // TODO(crbug.com/430889664): Clearing these fields as they're not currently
  // used for fetching PWM predictions. When alternative signature is deprecated
  // in favor of structural signature and three-bit hashes, we should update the
  // test to check that these fields are set correctly.
  request.mutable_experiments()->Clear();
  for (int i = 0; i < request.forms_size(); ++i) {
    request.mutable_forms(i)->clear_structural_signature();
    request.mutable_forms(i)->clear_three_bit_hashed_form_metadata();
    for (int j = 0; j < request.forms(i).fields_size(); ++j) {
      request.mutable_forms(i)
          ->mutable_fields(j)
          ->clear_three_bit_hashed_field_metadata();
    }
  }
  for (int i = 0; i < expected.forms_size(); ++i) {
    expected.mutable_forms(i)->clear_structural_signature();
    expected.mutable_forms(i)->clear_three_bit_hashed_form_metadata();
    for (int j = 0; j < expected.forms(i).fields_size(); ++j) {
      expected.mutable_forms(i)
          ->mutable_fields(j)
          ->clear_three_bit_hashed_field_metadata();
    }
  }

  return request.SerializeAsString() == expected.SerializeAsString();
}

// Verify that a site with password fields will query even in the presence
// of user defined autocomplete types.
IN_PROC_BROWSER_TEST_F(AutofillServerTest, AlwaysQueryForPasswordFields) {
  // Load the test page. Expect a query request upon loading the page.
  SetUrlContent("/test.html", R"(
      <form id=test_form>
        <input type=text id=one autocomplete=username>
        <input type=text id=two autocomplete=off>
        <input type=password id=three>
        <input type=submit>
      </form>
  )");

  AutofillPageQueryRequest query;
  query.set_client_version(std::string(GetProductNameAndVersionForUserAgent()));
  auto* query_form = query.add_forms();
  query_form->set_signature(4875414400744072230U);
  query_form->set_alternative_signature(130271417830211693U);

  query_form->add_fields()->set_signature(2594484045U);
  query_form->add_fields()->set_signature(2750915947U);
  query_form->add_fields()->set_signature(116843943U);

  WindowedNetworkObserver query_network_observer(EqualsQueryProto(query));
  NavigateToUrl("/test.html");
  query_network_observer.Wait();
}

}  // namespace
}  // namespace autofill
