// 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/glic/media/glic_media_link_helper.h"

#include "base/test/scoped_feature_list.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "content/public/browser/media_session.h"
#include "content/public/test/mock_media_session.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "media/base/media_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace glic {

namespace {

using ::testing::_;
using ::testing::Eq;
using ::testing::Return;

// Subclass for `GlicMediaLinkHelper` that lets us provide the `MediaSession`
// object directly, since trying to get MediaSession::FromWebContents() to
// return a mock is basically impossible without some fairly big changes.
class GlicMediaLinkHelperForTest : public GlicMediaLinkHelper {
 public:
  GlicMediaLinkHelperForTest(content::WebContents* web_contents,
                             content::MediaSession* media_session)
      : GlicMediaLinkHelper(web_contents), media_session_(media_session) {}

  content::MediaSession* GetMediaSessionIfExists() override {
    return media_session_;
  }

  void clear_media_session() { media_session_ = nullptr; }

 private:
  raw_ptr<content::MediaSession> media_session_ = nullptr;
};

class GlicMediaLinkHelperTest : public ChromeRenderViewHostTestHarness {
 public:
  GlicMediaLinkHelperTest() = default;
  ~GlicMediaLinkHelperTest() override = default;

  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    media_link_helper_ = std::make_unique<GlicMediaLinkHelperForTest>(
        web_contents(), &mock_media_session_);
  }

  void TearDown() override {
    media_link_helper_.reset();
    ChromeRenderViewHostTestHarness::TearDown();
  }

  content::MockMediaSession& mock_media_session() {
    return mock_media_session_;
  }

  GlicMediaLinkHelperForTest* media_link_helper() {
    return media_link_helper_.get();
  }

  void CreateAndAttachRoutedSubframe(const GURL& subframe_url) {
    // Set up a subframe with an embed URL.
    content::RenderFrameHost* subframe =
        content::NavigationSimulator::NavigateAndCommitFromDocument(
            subframe_url, content::RenderFrameHostTester::For(
                              web_contents()->GetPrimaryMainFrame())
                              ->AppendChild("subframe"));

    ON_CALL(mock_media_session(), GetRoutedFrame)
        .WillByDefault(Return(subframe));
  }

 protected:
  content::MockMediaSession mock_media_session_;
  std::unique_ptr<GlicMediaLinkHelperForTest> media_link_helper_;
};

TEST_F(GlicMediaLinkHelperTest, DifferentHostsReturnsFalse) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());

  // Case 1: Committed is example.com, target is youtube.com
  web_contents_tester->NavigateAndCommit(GURL("https://www.example.com/page"));
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123")));

  // Case 2: Different subdomains of YouTube are considered different hosts.
  web_contents_tester->NavigateAndCommit(
      GURL("https://m.youtube.com/watch?v=video123"));
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, SameOriginNoHelperReturnsFalse) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(GURL("https://www.example.com/page"));

  // example.com is not in g_origin_helpers
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.example.com/another_page")));
}

// --- YouTubeEmbedHelper Specific Tests (via MaybeReplaceNavigation) ---

TEST_F(GlicMediaLinkHelperTest, YouTubeEmbedHelper_FeatureDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(kMediaLinkEmbedHelper);

  // Navigate main frame to something else.
  content::WebContentsTester::For(web_contents())
      ->NavigateAndCommit(GURL("https://www.example.com/"));

  CreateAndAttachRoutedSubframe(GURL("https://www.youtube.com/embed/video123"));

  EXPECT_CALL(mock_media_session(), SeekTo(_)).Times(0);

  // The target is a regular YT link. Since the feature is off, this should
  // fall through to the normal helper, which will fail because the main
  // frame's host is example.com.
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeEmbedHelper_SuccessWithWatchTarget) {
  base::test::ScopedFeatureList feature_list(kMediaLinkEmbedHelper);

  content::WebContentsTester::For(web_contents())
      ->NavigateAndCommit(GURL("https://www.example.com/"));

  CreateAndAttachRoutedSubframe(GURL("https://www.youtube.com/embed/video123"));

  EXPECT_CALL(mock_media_session(), SeekTo(Eq(base::Seconds(30)))).Times(1);

  EXPECT_TRUE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeEmbedHelper_SuccessWithEmbedTarget) {
  base::test::ScopedFeatureList feature_list(kMediaLinkEmbedHelper);

  content::WebContentsTester::For(web_contents())
      ->NavigateAndCommit(GURL("https://www.example.com/"));

  CreateAndAttachRoutedSubframe(GURL("https://www.youtube.com/embed/video123"));

  EXPECT_CALL(mock_media_session(), SeekTo(Eq(base::Seconds(45)))).Times(1);

  EXPECT_TRUE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/embed/video123?t=45")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeEmbedHelper_MismatchedVideoId) {
  base::test::ScopedFeatureList feature_list(kMediaLinkEmbedHelper);

  content::WebContentsTester::For(web_contents())
      ->NavigateAndCommit(GURL("https://www.example.com/"));

  CreateAndAttachRoutedSubframe(GURL("https://www.youtube.com/embed/video123"));

  EXPECT_CALL(mock_media_session(), SeekTo(_)).Times(0);

  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=DIFFERENT&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeEmbedHelper_NoTimeParam) {
  base::test::ScopedFeatureList feature_list(kMediaLinkEmbedHelper);

  content::WebContentsTester::For(web_contents())
      ->NavigateAndCommit(GURL("https://www.example.com/"));

  CreateAndAttachRoutedSubframe(GURL("https://www.youtube.com/embed/video123"));

  EXPECT_CALL(mock_media_session(), SeekTo(_)).Times(0);

  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeEmbedHelper_NoRoutedFrame) {
  base::test::ScopedFeatureList feature_list(kMediaLinkEmbedHelper);

  ON_CALL(mock_media_session(), GetRoutedFrame).WillByDefault(Return(nullptr));

  content::WebContentsTester::For(web_contents())
      ->NavigateAndCommit(GURL("https://www.example.com/"));

  EXPECT_CALL(mock_media_session(), SeekTo(_)).Times(0);

  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_DifferentSchemeSucceeds) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("http://www.youtube.com/watch?v=video123"));

  EXPECT_CALL(mock_media_session(), SeekTo(Eq(base::Seconds(30)))).Times(1);

  EXPECT_TRUE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_MobileYouTubeIsNotHandled) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://m.youtube.com/watch?v=video123"));

  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://m.youtube.com/watch?v=video123&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_NoVideoIdInCommittedUrl) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  // Committed URL without 'v=' parameter
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/feed/subscriptions"));

  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_NoVideoIdInTargetUrl) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  // Target URL without 'v=' parameter
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/feed/subscriptions?t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_DifferentVideoIds) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=different&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_EmptyVideoId) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());

  // Committed URL with empty 'v='
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v="));
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=30")));

  // Target URL with empty 'v='
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_NoTimeParam) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  // Target URL without 't=' parameter
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&q=test")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_EmptyTimeParam) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  // Target URL with empty 't=' parameter
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_NonIntegerTimeParam) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  // Target URL with non-integer 't=' parameter
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=abc")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_NoMediaSession) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  // No MediaSession attached, so MediaSession::GetIfExists will return nullptr.
  media_link_helper()->clear_media_session();
  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=60")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_WithMediaSession) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  // Attach a mock media session and expect SeekTo to be called.
  EXPECT_CALL(mock_media_session(), SeekTo(Eq(base::Seconds(30)))).Times(1);

  EXPECT_TRUE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=30")));
}

TEST_F(GlicMediaLinkHelperTest, YouTubeHelper_WithMediaSession_ZeroTime) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  EXPECT_CALL(mock_media_session(), SeekTo(Eq(base::Seconds(0)))).Times(1);

  EXPECT_TRUE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=0")));
}

TEST_F(GlicMediaLinkHelperTest,
       YouTubeHelper_WithMediaSession_NegativeTimeParam) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  // Expect SeekTo to NOT be called because base::StringToUint fails for
  // negative numbers.
  EXPECT_CALL(mock_media_session(), SeekTo(_)).Times(0);

  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=-10")));
}

TEST_F(GlicMediaLinkHelperTest,
       YouTubeHelper_WithMediaSession_FractionalTimeParam) {
  auto* web_contents_tester = content::WebContentsTester::For(web_contents());
  web_contents_tester->NavigateAndCommit(
      GURL("https://www.youtube.com/watch?v=video123"));

  // Expect SeekTo to NOT be called because base::StringToUint fails for
  // non-integers, which also don't work for `t=` parameters.
  EXPECT_CALL(mock_media_session(), SeekTo(_)).Times(0);

  EXPECT_FALSE(media_link_helper()->MaybeReplaceNavigation(
      GURL("https://www.youtube.com/watch?v=video123&t=10.5")));
}

// Test fixture for static methods on GlicMediaLinkHelper.
class GlicMediaLinkHelperStaticTest : public testing::Test {};

TEST_F(GlicMediaLinkHelperStaticTest, ExtractVideoNameIfExists) {
  // Test case for a standard YouTube "watch" URL.
  GURL watch_url("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  std::optional<std::string> video_name =
      GlicMediaLinkHelper::ExtractVideoNameIfExists(watch_url);
  ASSERT_TRUE(video_name.has_value());
  EXPECT_EQ(*video_name, "dQw4w9WgXcQ");

  // Test case for a YouTube "embed" URL.
  GURL embed_url("https://www.youtube.com/embed/dQw4w9WgXcQ");
  video_name = GlicMediaLinkHelper::ExtractVideoNameIfExists(embed_url);
  ASSERT_TRUE(video_name.has_value());
  EXPECT_EQ(*video_name, "dQw4w9WgXcQ");

  // Test case with other query parameters.
  GURL watch_url_with_time("https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=42s");
  video_name =
      GlicMediaLinkHelper::ExtractVideoNameIfExists(watch_url_with_time);
  ASSERT_TRUE(video_name.has_value());
  EXPECT_EQ(*video_name, "dQw4w9WgXcQ");

  // Test case with no 'v' parameter.
  GURL no_v_param("https://www.youtube.com/watch?feature=youtu.be");
  video_name = GlicMediaLinkHelper::ExtractVideoNameIfExists(no_v_param);
  EXPECT_FALSE(video_name.has_value());

  // Test case with an empty 'v' parameter.
  GURL empty_v_param("https://www.youtube.com/watch?v=");
  video_name = GlicMediaLinkHelper::ExtractVideoNameIfExists(empty_v_param);
  EXPECT_FALSE(video_name.has_value());

  // Test case with an invalid path.
  GURL invalid_path("https://www.youtube.com/invalid/dQw4w9WgXcQ");
  video_name = GlicMediaLinkHelper::ExtractVideoNameIfExists(invalid_path);
  EXPECT_FALSE(video_name.has_value());
}

TEST_F(GlicMediaLinkHelperStaticTest, ExtractTimeFromQueryIfExists) {
  // Test case with a valid time parameter.
  GURL url_with_time("https://www.youtube.com/watch?v=123&t=42");
  std::optional<base::TimeDelta> time =
      GlicMediaLinkHelper::ExtractTimeFromQueryIfExists(url_with_time);
  ASSERT_TRUE(time.has_value());
  EXPECT_EQ(*time, base::Seconds(42));

  // Test case with "t" as the first parameter.
  GURL url_with_time_first("https://www.youtube.com/watch?t=123&v=456");
  time = GlicMediaLinkHelper::ExtractTimeFromQueryIfExists(url_with_time_first);
  ASSERT_TRUE(time.has_value());
  EXPECT_EQ(*time, base::Seconds(123));

  // Test case with no "t" parameter.
  GURL url_no_time("https://www.youtube.com/watch?v=123");
  time = GlicMediaLinkHelper::ExtractTimeFromQueryIfExists(url_no_time);
  EXPECT_FALSE(time.has_value());

  // Test case with an empty "t" parameter.
  GURL url_empty_time("https://www.youtube.com/watch?v=123&t=");
  time = GlicMediaLinkHelper::ExtractTimeFromQueryIfExists(url_empty_time);
  EXPECT_FALSE(time.has_value());

  // Test case with a non-numeric "t" parameter.
  GURL url_non_numeric_time("https://www.youtube.com/watch?v=123&t=abc");
  time =
      GlicMediaLinkHelper::ExtractTimeFromQueryIfExists(url_non_numeric_time);
  EXPECT_FALSE(time.has_value());

  // Test case with a negative "t" parameter.
  GURL url_negative_time("https://www.youtube.com/watch?v=123&t=-30");
  time = GlicMediaLinkHelper::ExtractTimeFromQueryIfExists(url_negative_time);
  EXPECT_FALSE(time.has_value());

  // Test case with a fractional "t" parameter.
  GURL url_fractional_time("https://www.youtube.com/watch?v=123&t=30.5");
  time = GlicMediaLinkHelper::ExtractTimeFromQueryIfExists(url_fractional_time);
  EXPECT_FALSE(time.has_value());

  // Test case with the "t" parameter that includes 's' at the end.
  GURL url_time_with_seconds("https://www.youtube.com/watch?v=123&t=30s");
  time =
      GlicMediaLinkHelper::ExtractTimeFromQueryIfExists(url_time_with_seconds);
  ASSERT_TRUE(time.has_value());
  EXPECT_EQ(*time, base::Seconds(30));
}

}  // namespace
}  // namespace glic
