// 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 <limits>
#include <string>
#include <string_view>
#include <vector>

#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/safe_base_name.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/apps/app_service/intent_util.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/apps/link_capturing/link_capturing_feature_test_support.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/fileapi/recent_file.h"
#include "chrome/browser/ash/fileapi/recent_model.h"
#include "chrome/browser/ash/fileapi/recent_model_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sharesheet/sharesheet_service.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/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/web_app_browsertest_base.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/test/base/ui_test_utils.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 "components/services/app_service/public/cpp/share_target.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/base/filename_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "storage/browser/file_system/file_system_context.h"
#include "ui/display/types/display_constants.h"
#include "url/gurl.h"

namespace {

base::FilePath PrepareWebShareDirectory(Profile* profile) {
  constexpr base::FilePath::CharType kWebShareDirname[] =
      FILE_PATH_LITERAL(".WebShare");
  const base::FilePath directory =
      file_manager::util::GetMyFilesFolderForProfile(profile).Append(
          kWebShareDirname);

  base::ScopedAllowBlockingForTesting allow_blocking;
  base::File::Error result = base::File::FILE_OK;
  EXPECT_TRUE(base::CreateDirectoryAndGetError(directory, &result));
  return directory;
}

void RemoveWebShareDirectory(const base::FilePath& directory) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  EXPECT_TRUE(base::DeletePathRecursively(directory));
}

base::FilePath StoreSharedFile(const base::FilePath& directory,
                               std::string_view name,
                               std::string_view content) {
  const base::FilePath path = directory.Append(name);
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::File file(path,
                  base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
  EXPECT_TRUE(file.WriteAtCurrentPosAndCheck(base::as_byte_span(content)));
  return path;
}

content::WebContents* LaunchWebAppWithIntent(Profile* profile,
                                             const webapps::AppId& app_id,
                                             apps::IntentPtr&& intent) {
  apps::AppLaunchParams params = apps::CreateAppLaunchParamsForIntent(
      app_id,
      /*event_flags=*/0, apps::LaunchSource::kFromSharesheet,
      display::kDefaultDisplayId, apps::LaunchContainer::kLaunchContainerWindow,
      std::move(intent), profile);

  web_app::WebAppProvider* provider =
      web_app::WebAppProvider::GetForLocalAppsUnchecked(profile);
  base::test::TestFuture<base::WeakPtr<BrowserWindowInterface>,
                         base::WeakPtr<content::WebContents>,
                         apps::LaunchContainer>
      future;
  provider->scheduler().LaunchAppWithCustomParams(std::move(params),
                                                  future.GetCallback());
  return future.template Get<1>().get();
}

content::EvalJsResult ReadTextContent(content::WebContents* web_contents,
                                      const char* id) {
  const std::string script =
      base::StringPrintf("document.getElementById('%s').textContent", id);
  return content::EvalJs(web_contents, script);
}

}  // namespace

namespace web_app {

class WebShareTargetBrowserTest : public WebAppBrowserTestBase {
 public:
  WebShareTargetBrowserTest() {
    scoped_feature_list_.InitWithFeaturesAndParameters(
        apps::test::GetFeaturesToEnableLinkCapturingUX(
            apps::test::LinkCapturingFeatureVersion::kV2DefaultOn),
        {});
  }

  GURL share_target_url() const {
    return embedded_test_server()->GetURL("/web_share_target/share.html");
  }

  content::WebContents* LaunchAppWithIntent(const webapps::AppId& app_id,
                                            apps::IntentPtr&& intent,
                                            const GURL& expected_url) {
    DCHECK(intent);
    ui_test_utils::UrlLoadObserver url_observer(expected_url);

    content::WebContents* const web_contents =
        LaunchWebAppWithIntent(profile(), app_id, std::move(intent));
    url_observer.Wait();
    EXPECT_EQ(expected_url, web_contents->GetVisibleURL());
    return web_contents;
  }

  unsigned NumRecentFiles(content::WebContents* contents) {
    unsigned result = std::numeric_limits<unsigned>::max();
    base::RunLoop run_loop;

    const scoped_refptr<storage::FileSystemContext> file_system_context =
        file_manager::util::GetFileSystemContextForRenderFrameHost(
            profile(), contents->GetPrimaryMainFrame());
    ash::RecentModelOptions options;
    options.source_specs.emplace_back(ash::RecentSourceSpec{
        .volume_type =
            extensions::api::file_manager_private::VolumeType::kTesting,
    });
    ash::RecentModelFactory::GetForProfile(profile())->GetRecentFiles(
        file_system_context.get(),
        /*origin=*/GURL(),
        /*query=*/"", options,
        base::BindLambdaForTesting(
            [&result, &run_loop](const std::vector<ash::RecentFile>& files) {
              result = files.size();
              run_loop.Quit();
            }));

    run_loop.Run();
    return result;
  }

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

IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareUsingFileURL) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL app_url =
      embedded_test_server()->GetURL("/web_share_target/charts.html");
  const webapps::AppId app_id =
      web_app::InstallWebAppFromManifest(browser(), app_url);

  base::ScopedAllowBlockingForTesting allow_blocking;
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());

  auto intent =
      std::make_unique<apps::Intent>(apps_util::kIntentActionSendMultiple);
  {
    const base::FilePath first_csv =
        StoreSharedFile(scoped_temp_dir.GetPath(), "first.csv", "1,2,3,4,5");
    const base::FilePath second_csv =
        StoreSharedFile(scoped_temp_dir.GetPath(), "second.csv", "6,7,8,9,0");

    std::vector<base::FilePath> file_paths({first_csv, second_csv});

    intent->mime_type = "text/csv";
    for (const base::FilePath& file_path : file_paths) {
      int64_t file_size = base::GetFileSize(file_path).value_or(0);
      auto file =
          std::make_unique<apps::IntentFile>(net::FilePathToFileURL(file_path));
      file->file_name = base::SafeBaseName::Create(file_path);
      file->file_size = file_size;
      file->mime_type = "text/csv";
      intent->files.push_back(std::move(file));
    }
  }

  content::WebContents* const web_contents =
      LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
  EXPECT_EQ("1,2,3,4,5 6,7,8,9,0", ReadTextContent(web_contents, "records"));
}

IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareImageWithText) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL app_url =
      embedded_test_server()->GetURL("/web_share_target/charts.html");
  const webapps::AppId app_id =
      web_app::InstallWebAppFromManifest(browser(), app_url);
  const base::FilePath directory = PrepareWebShareDirectory(profile());

  apps::IntentPtr intent;
  {
    const base::FilePath first_svg =
        StoreSharedFile(directory, "first.svg", "picture");

    std::vector<base::FilePath> file_paths({first_svg});
    std::vector<std::string> content_types(1, "image/svg+xml");
    intent = apps_util::CreateShareIntentFromFiles(
        profile(), std::move(file_paths), std::move(content_types),
        /*share_text=*/"Euclid https://example.org/",
        /*share_title=*/"Elements");
  }

  content::WebContents* const web_contents =
      LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
  EXPECT_EQ("picture", ReadTextContent(web_contents, "graphs"));

  EXPECT_EQ("Elements", ReadTextContent(web_contents, "headline"));
  EXPECT_EQ("Euclid", ReadTextContent(web_contents, "author"));
  EXPECT_EQ("https://example.org/", ReadTextContent(web_contents, "link"));
  EXPECT_EQ(NumRecentFiles(web_contents), 0U);

  RemoveWebShareDirectory(directory);
}

IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareAudio) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL app_url =
      embedded_test_server()->GetURL("/web_share_target/charts.html");
  const webapps::AppId app_id =
      web_app::InstallWebAppFromManifest(browser(), app_url);
  const base::FilePath directory = PrepareWebShareDirectory(profile());

  apps::IntentPtr intent;
  {
    const base::FilePath first_weba =
        StoreSharedFile(directory, "first.weba", "a");
    const base::FilePath second_weba =
        StoreSharedFile(directory, "second.weba", "b");
    const base::FilePath third_weba =
        StoreSharedFile(directory, "third.weba", "c");

    std::vector<base::FilePath> file_paths(
        {first_weba, second_weba, third_weba});
    std::vector<std::string> content_types(3, "audio/webm");
    intent = apps_util::CreateShareIntentFromFiles(
        profile(), std::move(file_paths), std::move(content_types));
    intent->share_text = "";
    intent->share_title = "";
  }

  content::WebContents* const web_contents =
      LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
  EXPECT_EQ("a b c", ReadTextContent(web_contents, "notes"));
  EXPECT_EQ(NumRecentFiles(web_contents), 0U);

  RemoveWebShareDirectory(directory);
}

IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, PostBlank) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL app_url =
      embedded_test_server()->GetURL("/web_share_target/poster.html");
  const webapps::AppId app_id =
      web_app::InstallWebAppFromManifest(browser(), app_url);

  apps::IntentPtr intent = apps_util::MakeShareIntent(
      /*text=*/std::string(),
      /*title=*/std::string());

  content::WebContents* const web_contents =
      LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
  // Poster web app's service worker detects omitted values.
  EXPECT_EQ("N/A", ReadTextContent(web_contents, "headline"));
  EXPECT_EQ("N/A", ReadTextContent(web_contents, "author"));
  EXPECT_EQ("N/A", ReadTextContent(web_contents, "link"));
}

IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, PostLink) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL app_url =
      embedded_test_server()->GetURL("/web_share_target/poster.html");
  const webapps::AppId app_id =
      web_app::InstallWebAppFromManifest(browser(), app_url);
  const apps::ShareTarget* share_target =
      WebAppProvider::GetForTest(browser()->GetProfile())
          ->registrar_unsafe()
          .GetAppShareTarget(app_id);
  EXPECT_EQ(share_target->method, apps::ShareTarget::Method::kPost);
  EXPECT_EQ(share_target->enctype, apps::ShareTarget::Enctype::kFormUrlEncoded);

  const std::string shared_title = "Hyperlink";
  const std::string shared_link = "https://example.org/a?b=c&d=e%20#f";

  apps::IntentPtr intent = apps_util::MakeShareIntent(
      /*text=*/shared_link,
      /*title=*/shared_title);

  content::WebContents* const web_contents =
      LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
  EXPECT_EQ("POST", ReadTextContent(web_contents, "method"));
  EXPECT_EQ("application/x-www-form-urlencoded",
            ReadTextContent(web_contents, "type"));

  EXPECT_EQ(shared_title, ReadTextContent(web_contents, "headline"));
  // Poster web app's service worker detects omitted value.
  EXPECT_EQ("N/A", ReadTextContent(web_contents, "author"));
  EXPECT_EQ(shared_link, ReadTextContent(web_contents, "link"));
}

IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, GetLink) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL app_url =
      embedded_test_server()->GetURL("/web_share_target/gatherer.html");
  const webapps::AppId app_id =
      web_app::InstallWebAppFromManifest(browser(), app_url);
  const apps::ShareTarget* share_target =
      WebAppProvider::GetForTest(browser()->GetProfile())
          ->registrar_unsafe()
          .GetAppShareTarget(app_id);
  EXPECT_EQ(share_target->method, apps::ShareTarget::Method::kGet);
  EXPECT_EQ(share_target->enctype, apps::ShareTarget::Enctype::kFormUrlEncoded);

  const std::string shared_title = "My News";
  const std::string shared_link = "http://example.com/news";
  const GURL expected_url(share_target_url().spec() +
                          "?headline=My+News&link=http://example.com/news");

  apps::IntentPtr intent = apps_util::MakeShareIntent(
      /*text=*/shared_link,
      /*title=*/shared_title);

  content::WebContents* const web_contents =
      LaunchAppWithIntent(app_id, std::move(intent), expected_url);
  EXPECT_EQ("GET", ReadTextContent(web_contents, "method"));
  EXPECT_EQ(expected_url.spec(), ReadTextContent(web_contents, "url"));

  EXPECT_EQ(shared_title, ReadTextContent(web_contents, "headline"));
  // Gatherer web app's service worker detects omitted value.
  EXPECT_EQ("N/A", ReadTextContent(web_contents, "author"));
  EXPECT_EQ(shared_link, ReadTextContent(web_contents, "link"));
}

}  // namespace web_app
