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

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/uuid.h"
#include "build/build_config.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sharing/shared_clipboard/remote_copy_message_handler.h"
#include "chrome/browser/sharing/sharing_service_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/sharing_message/shared_clipboard/remote_copy_handle_message_result.h"
#include "components/sharing_message/sharing_constants.h"
#include "components/sharing_message/sharing_fcm_handler.h"
#include "components/sharing_message/sharing_service.h"
#include "content/public/test/browser_test.h"
#include "net/http/http_status_code.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/clipboard/clipboard_monitor.h"
#include "ui/base/clipboard/clipboard_observer.h"
#include "ui/base/clipboard/test/clipboard_test_util.h"
#include "ui/base/clipboard/test/test_clipboard.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/message_center/public/cpp/notification.h"

namespace {
const char kDeviceName[] = "test device name";
const char16_t kDeviceName16[] = u"test device name";
const char kText[] = "test text";

class ClipboardObserver : public ui::ClipboardObserver {
 public:
  explicit ClipboardObserver(base::RepeatingClosure callback)
      : callback_(callback) {}

  ClipboardObserver(const ClipboardObserver&) = delete;
  ClipboardObserver& operator=(const ClipboardObserver&) = delete;

  void OnClipboardDataChanged() override { callback_.Run(); }

 private:
  base::RepeatingClosure callback_;
};

}  // namespace

// Browser tests for the Remote Copy feature.
class RemoteCopyBrowserTest : public InProcessBrowserTest {
 public:
  RemoteCopyBrowserTest() {
    server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTP);
    server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
    EXPECT_TRUE(server_->Start());
  }

  ~RemoteCopyBrowserTest() override = default;

  void SetUpOnMainThread() override {
    ui::TestClipboard::CreateForCurrentThread();
    notification_tester_ = std::make_unique<NotificationDisplayServiceTester>(
        browser()->GetProfile());
    sharing_service_ =
        SharingServiceFactory::GetForBrowserContext(browser()->GetProfile());
    auto* remote_copy_handler = static_cast<RemoteCopyMessageHandler*>(
        sharing_service_->GetSharingHandlerForTesting(
            components_sharing_message::SharingMessage::kRemoteCopyMessage));
    ASSERT_TRUE(remote_copy_handler);
    remote_copy_handler->set_allowed_origin_for_testing(server_->base_url());
  }

  void TearDownOnMainThread() override {
    notification_tester_.reset();
    ui::Clipboard::DestroyClipboardForCurrentThread();
  }

  gcm::IncomingMessage CreateMessage(const std::string& device_name,
                                     std::optional<std::string> text,
                                     std::optional<GURL> image_url) {
    components_sharing_message::SharingMessage sharing_message;
    sharing_message.set_sender_guid(
        base::Uuid::GenerateRandomV4().AsLowercaseString());
    sharing_message.set_sender_device_name(device_name);
    if (text) {
      sharing_message.mutable_remote_copy_message()->set_text(text.value());
    } else if (image_url) {
      sharing_message.mutable_remote_copy_message()->set_image_url(
          image_url.value().possibly_invalid_spec());
    }

    gcm::IncomingMessage incoming_message;
    std::string serialized_sharing_message;
    sharing_message.SerializeToString(&serialized_sharing_message);
    incoming_message.raw_data = serialized_sharing_message;
    return incoming_message;
  }

  void SendTextMessage(const std::string& device_name,
                       const std::string& text) {
    sharing_service_->GetFCMHandlerForTesting()->OnMessage(
        kSharingFCMAppID,
        CreateMessage(device_name, text, /*image_url=*/std::nullopt));
  }

  void SendImageMessage(const std::string& device_name, const GURL& image_url) {
    base::RunLoop run_loop;
    ClipboardObserver observer(run_loop.QuitClosure());
    ui::ClipboardMonitor::GetInstance()->AddObserver(&observer);
    sharing_service_->GetFCMHandlerForTesting()->OnMessage(
        kSharingFCMAppID,
        CreateMessage(device_name, /*text*/ std::nullopt, image_url));
    run_loop.Run();
    ui::ClipboardMonitor::GetInstance()->RemoveObserver(&observer);
  }

  std::vector<std::u16string> GetAvailableClipboardTypes() {
    return ui::clipboard_test_util::ReadAvailableTypes(
        ui::Clipboard::GetForCurrentThread(), ui::ClipboardBuffer::kCopyPaste,
        /* data_dst = */ nullptr);
  }

  std::string ReadClipboardText() {
    return base::UTF16ToUTF8(ui::clipboard_test_util::ReadText(
        ui::Clipboard::GetForCurrentThread(), ui::ClipboardBuffer::kCopyPaste,
        /* data_dst = */ nullptr));
  }

  SkBitmap ReadClipboardImage() {
    std::vector<uint8_t> png_data = ui::clipboard_test_util::ReadPng(
        ui::Clipboard::GetForCurrentThread(), ui::ClipboardBuffer::kCopyPaste,
        /*data_dst=*/nullptr);
    SkBitmap bitmap = gfx::PNGCodec::Decode(png_data);
    CHECK(!bitmap.isNull());
    return bitmap;
  }

  message_center::Notification GetNotification() {
    auto notifications = notification_tester_->GetDisplayedNotificationsForType(
        NotificationHandler::Type::SHARING);
    EXPECT_EQ(notifications.size(), 1u);
    return notifications[0];
  }

 protected:
  base::HistogramTester histograms_;
  std::unique_ptr<NotificationDisplayServiceTester> notification_tester_;
  raw_ptr<SharingService, DanglingUntriaged> sharing_service_;
  std::unique_ptr<net::EmbeddedTestServer> server_;
};

IN_PROC_BROWSER_TEST_F(RemoteCopyBrowserTest, Text) {
  // The clipboard is empty.
  ASSERT_TRUE(GetAvailableClipboardTypes().empty());

  // Send a message with text.
  SendTextMessage(kDeviceName, kText);

  // The text is in the clipboard and a notification is shown.
  std::vector<std::u16string> types = GetAvailableClipboardTypes();
  size_t expected_size = 1u;
  ASSERT_EQ(expected_size, types.size());
  ASSERT_EQ(ui::kMimeTypePlainText, base::UTF16ToASCII(types[0]));
  if (expected_size == 2u) {
    ASSERT_EQ(ui::kMimeTypeUtf8PlainText, base::UTF16ToASCII(types[1]));
  }
  ASSERT_EQ(kText, ReadClipboardText());
  message_center::Notification notification = GetNotification();
  ASSERT_EQ(l10n_util::GetStringFUTF16(
                IDS_SHARING_REMOTE_COPY_NOTIFICATION_TITLE_TEXT_CONTENT,
                kDeviceName16),
            notification.title());
  ASSERT_EQ(message_center::NOTIFICATION_TYPE_SIMPLE, notification.type());
}

IN_PROC_BROWSER_TEST_F(RemoteCopyBrowserTest, ImageUrl) {
  // The clipboard is empty.
  ASSERT_TRUE(GetAvailableClipboardTypes().empty());

  // Send a message with an image url.
  SendImageMessage(kDeviceName, server_->GetURL("/image_decoding/droids.jpg"));

  // The image is in the clipboard and a notification is shown.
  std::vector<std::u16string> types = GetAvailableClipboardTypes();
  ASSERT_EQ(1u, types.size());
  ASSERT_EQ(ui::kMimeTypePng, base::UTF16ToASCII(types[0]));
  SkBitmap bitmap = ReadClipboardImage();
  ASSERT_FALSE(bitmap.drawsNothing());
  ASSERT_EQ(2560, bitmap.width());
  ASSERT_EQ(1920, bitmap.height());
  message_center::Notification notification = GetNotification();
  ASSERT_EQ(l10n_util::GetStringFUTF16(
                IDS_SHARING_REMOTE_COPY_NOTIFICATION_TITLE_IMAGE_CONTENT,
                kDeviceName16),
            notification.title());
  ASSERT_EQ(message_center::NOTIFICATION_TYPE_SIMPLE, notification.type());
}

IN_PROC_BROWSER_TEST_F(RemoteCopyBrowserTest, TextThenImageUrl) {
  // The clipboard is empty.
  ASSERT_TRUE(GetAvailableClipboardTypes().empty());

  // Send a message with text.
  SendTextMessage(kDeviceName, kText);

  // The text is in the clipboard.
  std::vector<std::u16string> types = GetAvailableClipboardTypes();
  size_t expected_size = 1u;
  ASSERT_EQ(expected_size, types.size());
  ASSERT_EQ(ui::kMimeTypePlainText, base::UTF16ToASCII(types[0]));
  if (expected_size == 2u) {
    ASSERT_EQ(ui::kMimeTypeUtf8PlainText, base::UTF16ToASCII(types[1]));
  }
  ASSERT_EQ(kText, ReadClipboardText());

  // Send a message with an image url.
  SendImageMessage(kDeviceName, server_->GetURL("/image_decoding/droids.jpg"));

  // The image is in the clipboard and the text has been cleared.
  types = GetAvailableClipboardTypes();
  ASSERT_EQ(1u, types.size());
  ASSERT_EQ(ui::kMimeTypePng, base::UTF16ToASCII(types[0]));
  ASSERT_EQ(std::string(), ReadClipboardText());
}
