// Copyright 2025 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/loader/keep_alive_request_browsertest_util.h"

#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/ui/browser.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/keep_alive_url_loader_utils.h"
#include "content/public/test/test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "url/url_util.h"

std::string EncodeRelativeURL(std::string_view relative_url) {
  CHECK(relative_url.starts_with("/")) << relative_url;
  return url::EncodeUriComponent(relative_url);
}

ChromeKeepAliveRequestBrowserTestBase::ChromeKeepAliveRequestBrowserTestBase()
    : https_test_server_(std::make_unique<net::EmbeddedTestServer>(
          net::EmbeddedTestServer::TYPE_HTTPS)) {}

ChromeKeepAliveRequestBrowserTestBase::
    ~ChromeKeepAliveRequestBrowserTestBase() = default;

void ChromeKeepAliveRequestBrowserTestBase::SetUpOnMainThread() {
  PlatformBrowserTest::SetUpOnMainThread();
  host_resolver()->AddRule("*", "127.0.0.1");

  // Initialize an HTTPS server, as some in-browser throttles like
  // `variations::VariationsURLLoaderThrottle` only works in secure context.
  https_test_server_->AddDefaultHandlers(GetChromeTestDataDir());

  loaders_observer_ =
      std::make_unique<content::KeepAliveURLLoadersTestObserver>(
          web_contents()->GetBrowserContext());
}

void ChromeKeepAliveRequestBrowserTestBase::TearDownOnMainThread() {
  ASSERT_TRUE(server()->ShutdownAndWaitUntilComplete());
}

void ChromeKeepAliveRequestBrowserTestBase::SetUpCommandLine(
    base::CommandLine* command_line) {
  // Ignore all HTTPS certificate errors, as
  // `variations::VariationsURLLoaderThrottle` only works with google
  // domains, which are not covered by
  // `net::EmbeddedTestServer::CERT_TEST_NAMES`.
  command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}

void ChromeKeepAliveRequestBrowserTestBase::InitFeatureList(
    const std::vector<base::test::FeatureRefAndParams>&
        additional_features_and_params) {
  feature_list_.InitWithFeaturesAndParameters(
      content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(
          additional_features_and_params),
      content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
}

void ChromeKeepAliveRequestBrowserTestBase::
    LoadPageWithKeepAliveRequestAndSendResponseAfterUnload(
        const GURL& keepalive_page_url,
        net::test_server::ControllableHttpResponse* keepalive_request_handler,
        const std::string& response) {
  ASSERT_TRUE(content::NavigateToURL(web_contents(), keepalive_page_url));
  content::RenderFrameHostWrapper rfh_1(current_frame_host());
  // Ensure the current page can be unloaded instead of being cached.
  DisableBackForwardCache(web_contents());
  // Ensure the keepalive request is sent before leaving the current page.
  keepalive_request_handler->WaitForRequest();

  // Navigate to cross-origin page to ensure the 1st page can be unloaded.
  ASSERT_TRUE(content::NavigateToURL(web_contents(), GetCrossOriginPageURL()));
  // Ensure the 1st page has been unloaded.
  ASSERT_TRUE(rfh_1.WaitUntilRenderFrameDeleted());
  // The disconnected loader is still pending to receive response.

  // Sends back response to terminate in-browser request handling.
  keepalive_request_handler->Send(response);
  keepalive_request_handler->Done();
}

std::vector<std::unique_ptr<net::test_server::ControllableHttpResponse>>
ChromeKeepAliveRequestBrowserTestBase::RegisterRequestHandlers(
    const std::vector<std::string>& relative_urls) {
  std::vector<std::unique_ptr<net::test_server::ControllableHttpResponse>>
      handlers;
  for (const auto& relative_url : relative_urls) {
    handlers.emplace_back(
        std::make_unique<net::test_server::ControllableHttpResponse>(
            server(), relative_url));
  }
  return handlers;
}

GURL ChromeKeepAliveRequestBrowserTestBase::GetKeepAlivePageURL(
    const std::string& host,
    const std::string& target_relative_url,
    const std::string& method) {
  std::string url = base::StringPrintf(
      "/fetch-keepalive.html?target_url=%s&method=%s",
      EncodeRelativeURL(target_relative_url).c_str(), method.c_str());
  return server()->GetURL(host, url);
}

GURL ChromeKeepAliveRequestBrowserTestBase::GetCrossOriginPageURL() {
  return server()->GetURL(kSecondaryHost, "/title2.html");
}

void ChromeKeepAliveRequestBrowserTestBase::DisableBackForwardCache(
    content::WebContents* web_contents) {
  content::DisableBackForwardCacheForTesting(
      web_contents, content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
}

void ChromeKeepAliveRequestBrowserTestBase::SetUseHttps() {
  use_https_ = true;
}

content::WebContents* ChromeKeepAliveRequestBrowserTestBase::web_contents() {
  return browser()->tab_strip_model()->GetActiveWebContents();
}

content::RenderFrameHost*
ChromeKeepAliveRequestBrowserTestBase::current_frame_host() {
  return web_contents()->GetPrimaryMainFrame();
}

net::EmbeddedTestServer* ChromeKeepAliveRequestBrowserTestBase::server() {
  return use_https_ ? https_test_server_.get() : embedded_test_server();
}

content::KeepAliveURLLoadersTestObserver&
ChromeKeepAliveRequestBrowserTestBase::loaders_observer() {
  return *loaders_observer_;
}
