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

#include "content/browser/preloading/prerender/prerender_handle_impl.h"

#include <memory>
#include <optional>

#include "base/test/bind.h"
#include "content/browser/preloading/preload_pipeline_info_impl.h"
#include "content/browser/preloading/prerender/prerender_host.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/public/browser/preloading_trigger_type.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {
namespace {

// TODO(crbug.com/485415143): Add test cases for other public methods in
// PrerenderHandleImpl.
class TestPrerenderObserver : public PrerenderHandle::Observer {
 public:
  explicit TestPrerenderObserver(PrerenderHandle* handle) : handle_(handle) {
    handle_->AddObserver(this);
  }
  ~TestPrerenderObserver() override {
    if (handle_) {
      handle_->RemoveObserver(this);
    }
  }

  void OnLifecycleStateChanged(PrerenderLifecycleStatus status) override {
    last_status_ = status;
    statuses_.push_back(status);
  }

  std::optional<PrerenderLifecycleStatus> last_status() const {
    return last_status_;
  }
  const std::vector<PrerenderLifecycleStatus>& statuses() const {
    return statuses_;
  }

  bool HasSeenStatus(PrerenderLifecycleStatus status) const {
    for (auto s : statuses_) {
      if (s == status) {
        return true;
      }
    }
    return false;
  }

 private:
  raw_ptr<PrerenderHandle> handle_;
  std::optional<PrerenderLifecycleStatus> last_status_;
  std::vector<PrerenderLifecycleStatus> statuses_;
};

class PrerenderHandleImplTest : public RenderViewHostImplTestHarness {
 public:
  PrerenderHandleImplTest() = default;

  void SetUp() override {
    RenderViewHostImplTestHarness::SetUp();
    browser_context_ = std::make_unique<TestBrowserContext>();
    web_contents_delegate_ =
        std::make_unique<test::ScopedPrerenderWebContentsDelegate>(*contents());
    contents()->NavigateAndCommit(GURL("https://example.com/"));
  }

  void TearDown() override {
    web_contents_delegate_.reset();
    browser_context_.reset();
    RenderViewHostImplTestHarness::TearDown();
  }

  PrerenderHostRegistry& registry() {
    return *contents()->GetPrerenderHostRegistry();
  }

  PrerenderAttributes GeneratePrerenderAttributes(const GURL& url) {
    return PrerenderAttributes(
        url, PreloadingTriggerType::kEmbedder, "EmbedderSuffix", std::nullopt,
        Referrer(), std::nullopt, nullptr, contents()->GetWeakPtr(),
        ui::PAGE_TRANSITION_LINK,
        /*should_warm_up_compositor=*/false,
        /*should_prepare_paint_tree=*/false,
        blink::mojom::SpeculationAction::kPrerender,
        /*url_match_predicate=*/{},
        /*prerender_navigation_handle_callback=*/{},
        PreloadPipelineInfoImpl::Create(
            /*planned_max_preloading_type=*/PreloadingType::kPrerender),
        /*allow_reuse=*/false,
        /*form_submission=*/false);
  }

 private:
  std::unique_ptr<TestBrowserContext> browser_context_;
  std::unique_ptr<test::ScopedPrerenderWebContentsDelegate>
      web_contents_delegate_;
  test::ScopedPrerenderFeatureList prerender_feature_list_;
};

TEST_F(PrerenderHandleImplTest, OnResponseHeadersReceived) {
  const GURL kPrerenderingUrl("https://example.com/next");
  const PrerenderHostId prerender_host_id = registry().CreateAndStartHost(
      GeneratePrerenderAttributes(kPrerenderingUrl));
  ASSERT_TRUE(prerender_host_id);

  auto handle = std::make_unique<PrerenderHandleImpl>(
      registry().GetWeakPtr(), prerender_host_id, kPrerenderingUrl,
      std::nullopt);

  EXPECT_TRUE(handle->IsWaitingForResponseHeaders());

  TestPrerenderObserver observer(handle.get());

  PrerenderHost* host = registry().FindNonReservedHostById(prerender_host_id);
  ASSERT_TRUE(host);
  FrameTreeNode* ftn = FrameTreeNode::From(host->GetPrerenderedMainFrameHost());
  std::unique_ptr<NavigationSimulatorImpl> sim =
      NavigationSimulatorImpl::CreateFromPendingInFrame(ftn);

  sim->ReadyToCommit();

  EXPECT_FALSE(handle->IsWaitingForResponseHeaders());
  EXPECT_TRUE(
      observer.HasSeenStatus(PrerenderLifecycleStatus::kHTTPSuccessResponse));
}

TEST_F(PrerenderHandleImplTest, CallbackCalledOnFailure) {
  const GURL kPrerenderingUrl("https://example.com/next");
  const PrerenderHostId prerender_host_id = registry().CreateAndStartHost(
      GeneratePrerenderAttributes(kPrerenderingUrl));
  ASSERT_TRUE(prerender_host_id);

  auto handle = std::make_unique<PrerenderHandleImpl>(
      registry().GetWeakPtr(), prerender_host_id, kPrerenderingUrl,
      std::nullopt);

  EXPECT_TRUE(handle->IsWaitingForResponseHeaders());
  TestPrerenderObserver observer(handle.get());

  registry().CancelHost(prerender_host_id,
                        PrerenderFinalStatus::kRendererProcessCrashed);

  EXPECT_TRUE(observer.HasSeenStatus(PrerenderLifecycleStatus::kOtherFailure));
}

TEST_F(PrerenderHandleImplTest, GetPrerenderHostId) {
  const GURL kPrerenderingUrl("https://example.com/next");
  const PrerenderHostId prerender_host_id = registry().CreateAndStartHost(
      GeneratePrerenderAttributes(kPrerenderingUrl));
  ASSERT_TRUE(prerender_host_id);

  auto handle = std::make_unique<PrerenderHandleImpl>(
      registry().GetWeakPtr(), prerender_host_id, kPrerenderingUrl,
      std::nullopt);

  EXPECT_EQ(handle->GetPrerenderHostId(), prerender_host_id);
}

}  // namespace
}  // namespace content
