// Copyright 2020 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/apps/app_service/launch_utils.h"

#include "build/build_config.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/testing_profile.h"
#include "components/services/app_service/public/cpp/app_launch_params.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/intent.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/types/display_constants.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "chrome/browser/apps/app_service/publishers/app_publisher.h"
#include "chrome/browser/apps/link_capturing/link_capturing_feature_test_support.h"
#include "components/services/app_service/public/cpp/intent_filter_util.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

namespace apps {

class LaunchUtilsTest : public testing::Test {
 protected:
  apps::AppLaunchParams CreateLaunchParams(
      apps::LaunchContainer container,
      WindowOpenDisposition disposition,
      bool preferred_container,
      int64_t display_id = display::kInvalidDisplayId,
      apps::LaunchContainer fallback_container =
          apps::LaunchContainer::kLaunchContainerNone) {
    return apps::CreateAppIdLaunchParamsWithEventFlags(
        app_id, apps::GetEventFlags(disposition, preferred_container),
        apps::LaunchSource::kFromChromeInternal, display_id,
        fallback_container);
  }

  std::string app_id = "aaa";
  content::BrowserTaskEnvironment task_environment_;
  TestingProfile profile_;
};

TEST_F(LaunchUtilsTest, WindowContainerAndWindowDisposition) {
  auto container = apps::LaunchContainer::kLaunchContainerWindow;
  auto disposition = WindowOpenDisposition::NEW_WINDOW;
  auto params = CreateLaunchParams(container, disposition, false);

  EXPECT_EQ(container, params.container);
  EXPECT_EQ(disposition, params.disposition);
}

TEST_F(LaunchUtilsTest, TabContainerAndForegoundTabDisposition) {
  auto container = apps::LaunchContainer::kLaunchContainerTab;
  auto disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
  auto params = CreateLaunchParams(container, disposition, false);

  EXPECT_EQ(container, params.container);
  EXPECT_EQ(disposition, params.disposition);
}

TEST_F(LaunchUtilsTest, TabContainerAndBackgoundTabDisposition) {
  auto container = apps::LaunchContainer::kLaunchContainerTab;
  auto disposition = WindowOpenDisposition::NEW_BACKGROUND_TAB;
  auto params = CreateLaunchParams(container, disposition, false);

  EXPECT_EQ(container, params.container);
  EXPECT_EQ(disposition, params.disposition);
}

TEST_F(LaunchUtilsTest, PreferContainerWithTab) {
  auto container = apps::LaunchContainer::kLaunchContainerNone;
  auto disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
  auto preferred_container = apps::LaunchContainer::kLaunchContainerWindow;
  auto params =
      CreateLaunchParams(container, disposition, true,
                         display::kInvalidDisplayId, preferred_container);

  EXPECT_EQ(preferred_container, params.container);
  EXPECT_EQ(disposition, params.disposition);
}

TEST_F(LaunchUtilsTest, PreferContainerWithWindow) {
  auto container = apps::LaunchContainer::kLaunchContainerNone;
  auto disposition = WindowOpenDisposition::NEW_WINDOW;
  auto preferred_container = apps::LaunchContainer::kLaunchContainerWindow;
  auto params =
      CreateLaunchParams(container, disposition, true,
                         display::kInvalidDisplayId, preferred_container);

  EXPECT_EQ(preferred_container, params.container);
  EXPECT_EQ(WindowOpenDisposition::NEW_FOREGROUND_TAB, params.disposition);
}

TEST_F(LaunchUtilsTest, UseIntentFullUrlInLaunchParams) {
  auto disposition = WindowOpenDisposition::NEW_WINDOW;

  const GURL url = GURL("https://example.com/?query=1#frag");
  auto intent =
      std::make_unique<apps::Intent>(apps_util::kIntentActionView, url);

  auto params = apps::CreateAppLaunchParamsForIntent(
      app_id, apps::GetEventFlags(disposition, true),
      apps::LaunchSource::kFromChromeInternal, display::kInvalidDisplayId,
      apps::LaunchContainer::kLaunchContainerWindow, std::move(intent),
      &profile_);

  EXPECT_EQ(url, params.override_url);
}

TEST_F(LaunchUtilsTest, IntentFilesAreCopiedToLaunchParams) {
  auto disposition = WindowOpenDisposition::NEW_WINDOW;

  std::vector<apps::IntentFilePtr> files;
  std::string file_path = "filesystem:http://foo.com/test/foo.txt";
  auto file = std::make_unique<apps::IntentFile>(GURL(file_path));
  EXPECT_TRUE(file->url.is_valid());
  file->mime_type = "text/plain";
  files.push_back(std::move(file));
  auto intent = std::make_unique<apps::Intent>(apps_util::kIntentActionView,
                                               std::move(files));

  auto params = apps::CreateAppLaunchParamsForIntent(
      app_id, apps::GetEventFlags(disposition, true),
      apps::LaunchSource::kFromChromeInternal, display::kInvalidDisplayId,
      apps::LaunchContainer::kLaunchContainerWindow, std::move(intent),
      &profile_);

#if BUILDFLAG(IS_CHROMEOS)
  ASSERT_EQ(params.launch_files.size(), 1U);
  EXPECT_EQ("foo.txt", params.launch_files[0].MaybeAsASCII());
#else
  ASSERT_EQ(params.launch_files.size(), 0U);
#endif  // BUILDFLAG(IS_CHROMEOS)
}

TEST_F(LaunchUtilsTest, GetLaunchFilesFromCommandLine_NoAppID) {
  // Validate an empty vector is returned if there is
  // no AppID specified on the command line.
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  std::vector<base::FilePath> launch_files =
      apps::GetLaunchFilesFromCommandLine(command_line);
  EXPECT_EQ(launch_files.size(), 0U);
}

TEST_F(LaunchUtilsTest, GetLaunchFilesFromCommandLine_NoFiles) {
  // Validate an empty vector is returned if there are
  // no files specified on the command line.
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  command_line.AppendSwitchASCII(switches::kAppId, "test");
  std::vector<base::FilePath> launch_files =
      apps::GetLaunchFilesFromCommandLine(command_line);
  EXPECT_EQ(launch_files.size(), 0U);
}

TEST_F(LaunchUtilsTest, GetLaunchFilesFromCommandLine_SingleFile) {
  // Validate a vector with size 1 is returned, and the
  // contents match the command line parameter.
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  command_line.AppendSwitchASCII(switches::kAppId, "test");
  command_line.AppendArg("filename");
  std::vector<base::FilePath> launch_files =
      apps::GetLaunchFilesFromCommandLine(command_line);
  ASSERT_EQ(launch_files.size(), 1U);
  EXPECT_EQ(launch_files[0], base::FilePath(FILE_PATH_LITERAL("filename")));
}

TEST_F(LaunchUtilsTest, GetLaunchFilesFromCommandLine_MultipleFiles) {
  // Validate a vector with size 2 is returned, and the
  // contents match the command line parameter.
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  command_line.AppendSwitchASCII(switches::kAppId, "test");
  command_line.AppendArg("filename");
  command_line.AppendArg("filename2");
  std::vector<base::FilePath> launch_files =
      apps::GetLaunchFilesFromCommandLine(command_line);
  ASSERT_EQ(launch_files.size(), 2U);
  EXPECT_EQ(launch_files[0], base::FilePath(FILE_PATH_LITERAL("filename")));
  EXPECT_EQ(launch_files[1], base::FilePath(FILE_PATH_LITERAL("filename2")));
}

TEST_F(LaunchUtilsTest, GetLaunchFilesFromCommandLine_FileProtocol) {
  // Validate a vector with size 1 is returned, and the
  // contents match the command line parameter. This uses
  // the file protocol to reference the file.
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  command_line.AppendSwitchASCII(switches::kAppId, "test");
  command_line.AppendArg("file://filename");
  std::vector<base::FilePath> launch_files =
      apps::GetLaunchFilesFromCommandLine(command_line);
  ASSERT_EQ(launch_files.size(), 1U);
  EXPECT_EQ(launch_files[0],
            base::FilePath(FILE_PATH_LITERAL("file://filename")));
}

// Verifies that a non-file protocol is not treated as a filename.
TEST_F(LaunchUtilsTest, GetLaunchFilesFromCommandLine_CustomProtocol) {
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  command_line.AppendSwitchASCII(switches::kAppId, "test");
  command_line.AppendArg("web+test://filename");
  std::vector<base::FilePath> launch_files =
      apps::GetLaunchFilesFromCommandLine(command_line);
  EXPECT_EQ(0U, launch_files.size());
}

#if BUILDFLAG(IS_CHROMEOS)
// Fake AppPublisher for tracking app launches.
class FakePublisher : public AppPublisher {
 public:
  explicit FakePublisher(AppServiceProxy* proxy) : AppPublisher(proxy) {
    RegisterPublisher(AppType::kWeb);
  }

  void PublishAppWithUrlScope(std::string app_id, GURL scope) {
    AppPtr app = std::make_unique<App>(AppType::kWeb, app_id);
    app->readiness = Readiness::kReady;
    app->handles_intents = true;

    app->intent_filters.emplace();
    app->intent_filters->push_back(apps_util::MakeIntentFilterForUrlScope(
        scope, /*omit_port_for_testing=*/true));

    std::vector<AppPtr> apps;
    apps.push_back(std::move(app));
    AppPublisher::Publish(std::move(apps), AppType::kWeb,
                          /*should_notify_initialized=*/true);
  }

  const std::string& last_launched_app() { return last_launched_app_; }

  // AppPublisher:
  void Launch(const std::string& app_id,
              int32_t event_flags,
              LaunchSource launch_source,
              WindowInfoPtr window_info) override {}
  void LaunchAppWithParams(AppLaunchParams&& params,
                           LaunchCallback callback) override {}

  void LaunchAppWithIntent(const std::string& app_id,
                           int32_t event_flags,
                           IntentPtr intent,
                           LaunchSource launch_source,
                           WindowInfoPtr window_info,
                           LaunchCallback callback) override {
    last_launched_app_ = app_id;
  }

 private:
  std::string last_launched_app_;
};

class MockNewWindowDelegate
    : public testing::NiceMock<ash::TestNewWindowDelegate> {
 public:
  // TestNewWindowDelegate:
  MOCK_METHOD(void,
              OpenUrl,
              (const GURL& url, OpenUrlFrom from, Disposition disposition),
              (override));
};

class LaunchUtilsNewWindowTest : public LaunchUtilsTest {
 public:
  MockNewWindowDelegate& new_window_delegate() { return new_window_delegate_; }

 private:
  MockNewWindowDelegate new_window_delegate_;
};

TEST_F(LaunchUtilsNewWindowTest,
       MaybeLaunchPreferredAppForUrl_LaunchesPreferred) {
  FakePublisher publisher(AppServiceProxyFactory::GetForProfile(&profile_));
  publisher.PublishAppWithUrlScope("abc", GURL("https://www.example.com/"));
  ASSERT_EQ(test::EnableLinkCapturingByUser(&profile_, "abc"), base::ok());

  MaybeLaunchPreferredAppForUrl(&profile_, GURL("https://www.example.com/foo/"),
                                LaunchSource::kFromTest);

  ASSERT_EQ(publisher.last_launched_app(), "abc");
}

TEST_F(LaunchUtilsNewWindowTest,
       MaybeLaunchPreferredAppForUrl_LaunchesBrowserIfNoPreferredApp) {
  FakePublisher publisher(AppServiceProxyFactory::GetForProfile(&profile_));
  // Do not mark this app as preferred. The browser should be opened instead.
  publisher.PublishAppWithUrlScope("abc", GURL("https://www.example.com/"));

  EXPECT_CALL(
      new_window_delegate(),
      OpenUrl(GURL("https://www.example.com/foo/"), testing::_, testing::_));

  MaybeLaunchPreferredAppForUrl(&profile_, GURL("https://www.example.com/foo/"),
                                LaunchSource::kFromTest);
}

TEST_F(LaunchUtilsNewWindowTest, LaunchUrlInInstalledAppOrBrowser_OneApp) {
  FakePublisher publisher(AppServiceProxyFactory::GetForProfile(&profile_));
  publisher.PublishAppWithUrlScope("abc", GURL("https://www.example.com/"));

  LaunchUrlInInstalledAppOrBrowser(
      &profile_, GURL("https://www.example.com/foo"), LaunchSource::kFromTest);

  ASSERT_EQ(publisher.last_launched_app(), "abc");
}

TEST_F(LaunchUtilsNewWindowTest,
       LaunchUrlInInstalledAppOrBrowser_TwoAppsNoPreferred) {
  FakePublisher publisher(AppServiceProxyFactory::GetForProfile(&profile_));

  publisher.PublishAppWithUrlScope("abc", GURL("https://www.example.com/"));
  publisher.PublishAppWithUrlScope("def", GURL("https://www.example.com/app/"));

  EXPECT_CALL(
      new_window_delegate(),
      OpenUrl(GURL("https://www.example.com/app/"), testing::_, testing::_));

  LaunchUrlInInstalledAppOrBrowser(
      &profile_, GURL("https://www.example.com/app/"), LaunchSource::kFromTest);
}

TEST_F(LaunchUtilsNewWindowTest,
       LaunchUrlInInstalledAppOrBrowser_MultipleApps_LaunchesPreferred) {
  FakePublisher publisher(AppServiceProxyFactory::GetForProfile(&profile_));

  publisher.PublishAppWithUrlScope("abc", GURL("https://www.example.com/"));
  publisher.PublishAppWithUrlScope("def", GURL("https://www.example.com/app/"));

  // Enable the link capturing preference for one of the apps. This app should
  // be launched by `LaunchUrlInInstalledAppOrBrowser`.
  ASSERT_EQ(test::EnableLinkCapturingByUser(&profile_, "def"), base::ok());

  LaunchUrlInInstalledAppOrBrowser(
      &profile_, GURL("https://www.example.com/app/"), LaunchSource::kFromTest);

  ASSERT_EQ(publisher.last_launched_app(), "def");
}

TEST_F(LaunchUtilsNewWindowTest,
       LaunchUrlInInstalledAppOrBrowser_NoApp_LaunchesInBrowser) {
  EXPECT_CALL(new_window_delegate(),
              OpenUrl(GURL("https://www.example.com"), testing::_, testing::_));

  LaunchUrlInInstalledAppOrBrowser(&profile_, GURL("https://www.example.com/"),
                                   LaunchSource::kFromTest);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace apps
