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

#include "base/test/scoped_feature_list.h"
#include "base/types/expected.h"
#include "base/unguessable_token.h"
#include "chrome/browser/apps/intent_helper/preferred_apps_test_util.h"
#include "chrome/browser/apps/link_capturing/link_capturing_feature_test_support.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/web_applications/web_app_browsertest_base.h"
#include "chrome/browser/web_applications/web_app_tab_helper.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/media_session/public/cpp/features.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
#include "url/third_party/mozilla/url_parse.h"
#include "url/url_canon.h"

namespace web_app {

namespace {

const char kAudioFocusTestPageURL[] =
    "/extensions/audio_focus_web_app/main.html";

}  // namespace

// WebAppAudioFocusBrowserTest test that PWAs have separate audio
// focus from the rest of the browser.
class WebAppAudioFocusBrowserTest
    : public WebAppBrowserTestBase,
      public testing::WithParamInterface<
          apps::test::LinkCapturingFeatureVersion> {
 public:
  WebAppAudioFocusBrowserTest() = default;
  ~WebAppAudioFocusBrowserTest() override = default;

  apps::test::LinkCapturingFeatureVersion GetLinkCapturingVersion() const {
    return GetParam();
  }

  void SetUp() override {
    std::vector<base::test::FeatureRefAndParams> features =
        apps::test::GetFeaturesToEnableLinkCapturingUX(
            GetLinkCapturingVersion());

    features.emplace_back(media_session::features::kMediaSessionService,
                          base::FieldTrialParams());
    features.emplace_back(media_session::features::kAudioFocusEnforcement,
                          base::FieldTrialParams());
    features.emplace_back(media_session::features::kAudioFocusSessionGrouping,
                          base::FieldTrialParams());

    scoped_feature_list_.InitWithFeaturesAndParameters(
        features, /*disabled_features=*/{});

    WebAppBrowserTestBase::SetUp();
  }

  bool IsPaused(content::WebContents* web_contents) {
    return content::EvalJs(web_contents, "isPaused()").ExtractBool();
  }

  bool WaitForPause(content::WebContents* web_contents) {
    return content::EvalJs(web_contents, "waitForPause()").ExtractBool();
  }

  bool StartPlaying(content::WebContents* web_contents) {
    return content::EvalJs(web_contents, "startPlaying()").ExtractBool();
  }

  content::WebContents* AddTestPageTabAtIndex(int index) {
    EXPECT_TRUE(AddTabAtIndex(
        index, embedded_test_server()->GetURL(kAudioFocusTestPageURL),
        ui::PAGE_TRANSITION_TYPED));
    content::WebContents* tab =
        browser()->tab_strip_model()->GetActiveWebContents();
    EXPECT_TRUE(content::WaitForLoadStop(tab));
    return tab;
  }

  const base::UnguessableToken& GetAudioFocusGroupId(
      content::WebContents* web_contents) {
    WebAppTabHelper* helper = WebAppTabHelper::FromWebContents(web_contents);
    return helper->GetAudioFocusGroupIdForTesting();
  }

  // Simulates a page calling window.open on an URL and waits for the
  // navigation.
  content::WebContents* OpenWindow(content::WebContents* contents,
                                   bool aux,
                                   const GURL& url) {
    content::WebContentsAddedObserver tab_added_observer;
    EXPECT_TRUE(
        content::ExecJs(contents, "window.open('" + url.spec() + "', '', '" +
                                      (aux ? "opener" : "noopener") + "')"));
    content::WebContents* new_contents = tab_added_observer.GetWebContents();
    EXPECT_TRUE(new_contents);
    WaitForLoadStop(new_contents);

    EXPECT_EQ(url, contents->GetController().GetLastCommittedEntry()->GetURL());
    EXPECT_EQ(
        content::PAGE_TYPE_NORMAL,
        new_contents->GetController().GetLastCommittedEntry()->GetPageType());
    if (aux) {
      EXPECT_EQ(contents->GetPrimaryMainFrame()->GetSiteInstance(),
                new_contents->GetPrimaryMainFrame()->GetSiteInstance());
    }
    return new_contents;
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_P(WebAppAudioFocusBrowserTest, AppHasDifferentAudioFocus) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL app_url = embedded_test_server()->GetURL(kAudioFocusTestPageURL);

  webapps::AppId app_id = InstallPWA(app_url);

  // Launch browser with media page.
  content::WebContents* tab1 = AddTestPageTabAtIndex(0);

  // Start the test page playing.
  EXPECT_TRUE(StartPlaying(tab1));

  // Launch a second tab in the browser.
  content::WebContents* tab2 = AddTestPageTabAtIndex(0);

  // Start the test page playing and check that both tabs now have focus.
  EXPECT_TRUE(StartPlaying(tab2));
  EXPECT_FALSE(IsPaused(tab1));

  // Check that the two tabs have no group id.
  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusGroupId(tab1));
  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusGroupId(tab2));

  // Launch the PWA.
  BrowserWindowInterface* app_browser = LaunchWebAppBrowserAndWait(app_id);
  content::WebContents* web_contents =
      app_browser->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(content::WaitForLoadStop(web_contents));

  // Start the PWA playing and check that it has a group id.
  EXPECT_TRUE(StartPlaying(web_contents));
  const base::UnguessableToken& group_id = GetAudioFocusGroupId(web_contents);
  EXPECT_NE(base::UnguessableToken::Null(), group_id);

  // Check that the hosted app took audio focus from the browser tabs.
  EXPECT_TRUE(WaitForPause(tab1));
  EXPECT_TRUE(WaitForPause(tab2));

  // Open captured window, which should share the same group id if capturing
  // is enabled.
  {
    content::WebContents* new_contents =
        OpenWindow(web_contents, /*aux=*/false, app_url);
    if (GetLinkCapturingVersion() ==
        apps::test::LinkCapturingFeatureVersion::kV2DefaultOn) {
      EXPECT_EQ(group_id, GetAudioFocusGroupId(new_contents));
    } else {
      EXPECT_EQ(base::UnguessableToken::Null(),
                GetAudioFocusGroupId(new_contents));
    }
  }

#if !BUILDFLAG(IS_CHROMEOS)
  // Open an auxiliary window, which should also open in an app window and share
  // the group id.
  // TODO(crbug.com/392106502): Fix this test and enable once auxiliary context
  // support is added to PWAs and works on ChromeOS.
  {
    content::WebContents* new_contents =
        OpenWindow(web_contents, /*aux=*/true, app_url);
    EXPECT_EQ(group_id, GetAudioFocusGroupId(new_contents));
  }
#endif

  ASSERT_EQ(apps::test::DisableLinkCapturingByUser(profile(), app_id),
            base::ok());

  // Without capturing, new window will open in the browser so it should have no
  // group id.
  {
    content::WebContents* new_contents =
        OpenWindow(web_contents, /*aux=*/false, app_url);
    EXPECT_EQ(base::UnguessableToken::Null(),
              GetAudioFocusGroupId(new_contents));
  }

  // Navigate inside the PWA and make sure we keep the same group id.
  {
    GURL::Replacements replacements;
    replacements.SetQueryStr("t=1");
    GURL new_url =
        web_contents->GetLastCommittedURL().ReplaceComponents(replacements);
    ASSERT_TRUE(NavigateInRenderer(web_contents, new_url));
    EXPECT_EQ(group_id, GetAudioFocusGroupId(web_contents));
  }

  // Launch a second window for the PWA. It should have the same group id.
  {
    BrowserWindowInterface* second_app_browser =
        LaunchWebAppBrowserAndWait(app_id);

    content::WebContents* new_contents =
        second_app_browser->tab_strip_model()->GetActiveWebContents();
    EXPECT_TRUE(content::WaitForLoadStop(new_contents));

    EXPECT_EQ(group_id, GetAudioFocusGroupId(new_contents));
  }

  // Navigate away and check that the group id is still the same because we are
  // part of the same window.
  // TODO(crbug.com/40180004): Understand why this returns false.
  ASSERT_FALSE(
      NavigateInRenderer(web_contents, GURL("https://www.example.com")));
  EXPECT_EQ(group_id, GetAudioFocusGroupId(web_contents));
}

IN_PROC_BROWSER_TEST_P(WebAppAudioFocusBrowserTest, WebAppHasSameAudioFocus) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL app_url = embedded_test_server()->GetURL(kAudioFocusTestPageURL);

  webapps::AppId app_id = InstallPWA(app_url);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), app_url));
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(content::WaitForLoadStop(web_contents));

  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusGroupId(web_contents));
}

INSTANTIATE_TEST_SUITE_P(
    All,
    WebAppAudioFocusBrowserTest,
    testing::Values(apps::test::LinkCapturingFeatureVersion::kV2DefaultOff,
                    apps::test::LinkCapturingFeatureVersion::kV2DefaultOn));

}  // namespace web_app
