// 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 "components/guest_contents/browser/guest_contents_handle.h"

#include <algorithm>

#include "base/location.h"
#include "base/test/scoped_feature_list.h"
#include "content/public/common/content_features.h"
#include "content/public/test/test_renderer_host.h"

namespace guest_contents {

class GuestContentsHandleTest : public content::RenderViewHostTestHarness {
 public:
  void SetUp() override {
    content::RenderViewHostTestHarness::SetUp();
    guest_web_contents_ = CreateTestWebContents();
    content::RenderFrameHostTester::For(
        guest_web_contents_->GetPrimaryMainFrame())
        ->InitializeRenderFrameIfNeeded();
    outer_web_contents_ = CreateTestWebContents();
    content::RenderFrameHostTester* outer_rfh_tester =
        content::RenderFrameHostTester::For(
            outer_web_contents_->GetPrimaryMainFrame());
    outer_rfh_tester->InitializeRenderFrameIfNeeded();
    outer_delegate_rfh_ = outer_rfh_tester->AppendChild("outer_delegate");
    ASSERT_TRUE(outer_delegate_rfh_);
  }

  void TearDown() override {
    outer_delegate_rfh_ = nullptr;
    guest_web_contents_.reset();
    outer_web_contents_.reset();
    content::RenderViewHostTestHarness::TearDown();
  }

  std::unique_ptr<content::WebContents>& guest_web_contents() {
    return guest_web_contents_;
  }
  std::unique_ptr<content::WebContents>& outer_web_contents() {
    return outer_web_contents_;
  }
  content::RenderFrameHost& outer_delegate_rfh() {
    return *outer_delegate_rfh_;
  }

 protected:
  std::unique_ptr<content::WebContents> guest_web_contents_;
  std::unique_ptr<content::WebContents> outer_web_contents_;
  raw_ptr<content::RenderFrameHost> outer_delegate_rfh_;

 private:
  base::test::ScopedFeatureList scoped_feature_list_{
    features::kAttachUnownedInnerWebContents};
};

TEST_F(GuestContentsHandleTest, CreateAndGetById) {
  GuestContentsHandle* handle =
      GuestContentsHandle::CreateForWebContents(guest_web_contents().get());
  ASSERT_TRUE(handle);
  EXPECT_EQ(handle,
            GuestContentsHandle::FromWebContents(guest_web_contents().get()));

  GuestId id = handle->id();
  EXPECT_EQ(handle, GuestContentsHandle::FromID(id));

  // Test invalid ID
  EXPECT_EQ(nullptr,
            GuestContentsHandle::FromID(base::UnguessableToken::Create()));
  EXPECT_EQ(nullptr, GuestContentsHandle::FromID(base::UnguessableToken()));

  // Destroy the WebContents, which should destroy the handle
  guest_web_contents().reset();

  // Handle should no longer be findable by ID
  EXPECT_EQ(nullptr, GuestContentsHandle::FromID(id));
}

TEST_F(GuestContentsHandleTest, UniqueIds) {
  GuestContentsHandle* handle1 =
      GuestContentsHandle::CreateForWebContents(guest_web_contents().get());
  ASSERT_TRUE(handle1);

  // Create a second guest WebContents and handle
  std::unique_ptr<content::WebContents> guest_web_contents2 =
      CreateTestWebContents();
  GuestContentsHandle* handle2 =
      GuestContentsHandle::CreateForWebContents(guest_web_contents2.get());
  ASSERT_TRUE(handle2);

  EXPECT_NE(handle1->id(), handle2->id());

  // Clean up second guest
  guest_web_contents2.reset();
  // First handle should still exist
  EXPECT_EQ(handle1, GuestContentsHandle::FromID(handle1->id()));
}

TEST_F(GuestContentsHandleTest, AttachAndDetach) {
  GuestContentsHandle* handle =
      GuestContentsHandle::CreateForWebContents(guest_web_contents().get());
  ASSERT_TRUE(handle);

  handle->AttachToOuterWebContents(&outer_delegate_rfh());
  EXPECT_EQ(guest_web_contents()->GetOuterWebContents(),
            outer_web_contents().get());
  EXPECT_TRUE(std::ranges::contains(outer_web_contents()->GetInnerWebContents(),
                                    guest_web_contents().get()));

  handle->DetachFromOuterWebContents();
  EXPECT_EQ(nullptr, guest_web_contents()->GetOuterWebContents());
  EXPECT_TRUE(outer_web_contents()->GetInnerWebContents().empty());
}

TEST_F(GuestContentsHandleTest, DestroyGuestContents) {
  GuestContentsHandle* handle =
      GuestContentsHandle::CreateForWebContents(guest_web_contents().get());
  ASSERT_TRUE(handle);

  handle->AttachToOuterWebContents(&outer_delegate_rfh());
  EXPECT_EQ(guest_web_contents()->GetOuterWebContents(),
            outer_web_contents().get());
  EXPECT_TRUE(std::ranges::contains(outer_web_contents()->GetInnerWebContents(),
                                    guest_web_contents().get()));

  // Destroy the guest WebContents while it is still attached to the outer
  // WebContents.
  guest_web_contents().reset();
  EXPECT_TRUE(outer_web_contents()->GetInnerWebContents().empty());
}

TEST_F(GuestContentsHandleTest, DestroyOuterWebContents) {
  GuestContentsHandle* handle =
      GuestContentsHandle::CreateForWebContents(guest_web_contents().get());
  ASSERT_TRUE(handle);

  handle->AttachToOuterWebContents(&outer_delegate_rfh());
  EXPECT_EQ(guest_web_contents()->GetOuterWebContents(),
            outer_web_contents().get());

  // Destroy the outer WebContents while it still has a guest WebContents.
  outer_delegate_rfh_ = nullptr;
  outer_web_contents().reset();
  EXPECT_EQ(nullptr, guest_web_contents()->GetOuterWebContents());
}

}  // namespace guest_contents
