// 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 <memory>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/scoped_temp_dir.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/send_tab_to_self/send_tab_to_self_util.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/sync/test/integration/encryption_helper.h"
#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/browser_sync/browser_sync_switches.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/sync/base/command_line_switches.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/service/sync_service_impl.h"
#include "content/public/test/browser_test.h"

// The local sync backend is currently only supported on Windows, Mac, Linux.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

namespace {

using syncer::SyncServiceImpl;

constexpr char kTestPassphrase[] = "hunter2";

class SyncTransportActiveChecker : public SingleClientStatusChangeChecker {
 public:
  explicit SyncTransportActiveChecker(SyncServiceImpl* service)
      : SingleClientStatusChangeChecker(service) {}

  bool IsExitConditionSatisfied(std::ostream* os) override {
    *os << "Waiting for sync transport to become active";
    return service()->GetTransportState() ==
           syncer::SyncService::TransportState::ACTIVE;
  }
};

// This test verifies some basic functionality of local sync, used for roaming
// profiles (enterprise use-case).
class LocalSyncTest : public InProcessBrowserTest {
 public:
  LocalSyncTest(const LocalSyncTest&) = delete;
  LocalSyncTest& operator=(const LocalSyncTest&) = delete;

 protected:
  LocalSyncTest() {
    EXPECT_TRUE(local_sync_backend_dir_.CreateUniqueTempDir());
  }

  ~LocalSyncTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // By default on Window OS local sync backend uses roaming profile. It can
    // lead to problems if some tests run simultaneously and use the same
    // roaming profile.
    base::FilePath file = local_sync_backend_dir_.GetPath().Append(
        FILE_PATH_LITERAL("profile.pb"));
    command_line->AppendSwitchASCII(switches::kLocalSyncBackendDir,
                                    file.MaybeAsASCII());
    command_line->AppendSwitch(switches::kEnableLocalSyncBackend);
    command_line->AppendSwitchASCII(syncer::kSyncDeferredStartupTimeoutSeconds,
                                    "0");
  }

 private:
  base::ScopedTempDir local_sync_backend_dir_;
};

IN_PROC_BROWSER_TEST_F(LocalSyncTest, ShouldStart) {
  SyncServiceImpl* service =
      SyncServiceFactory::GetAsSyncServiceImplForProfileForTesting(
          browser()->GetProfile());

  // Wait until the first sync cycle is completed.
  ASSERT_TRUE(SyncTransportActiveChecker(service).Wait());

  EXPECT_TRUE(service->IsLocalSyncEnabled());
  EXPECT_FALSE(service->IsSyncFeatureEnabled());
  EXPECT_FALSE(service->IsSyncFeatureActive());
  EXPECT_FALSE(service->GetUserSettings()->IsInitialSyncFeatureSetupComplete());

  // Verify that the expected set of data types successfully started up.
  // If this test fails after adding a new data type, carefully consider whether
  // the type should be enabled in Local Sync mode, i.e. for roaming profiles on
  // Windows.
  syncer::DataTypeSet expected_active_data_types = {
      syncer::BOOKMARKS,
      syncer::READING_LIST,
      syncer::PREFERENCES,
      syncer::PASSWORDS,
      syncer::AUTOFILL_PROFILE,
      syncer::AUTOFILL,
      syncer::THEMES,
      syncer::EXTENSIONS,
      syncer::SAVED_TAB_GROUP,
      syncer::SEARCH_ENGINES,
      syncer::SESSIONS,
      syncer::APPS,
      syncer::APP_SETTINGS,
      syncer::EXTENSION_SETTINGS,
      syncer::DEVICE_INFO,
      syncer::PRIORITY_PREFERENCES,
      syncer::WEBAUTHN_CREDENTIAL,
      syncer::WEB_APPS,
      syncer::NIGORI};

  expected_active_data_types.Put(syncer::AUTOFILL_WALLET_CREDENTIAL);

  // The dictionary is currently only synced on Windows and Linux.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
  expected_active_data_types.Put(syncer::DICTIONARY);
#endif
  EXPECT_EQ(service->GetActiveDataTypes(), expected_active_data_types);

  // Verify certain features are disabled.
  EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::USER_CONSENTS));
  EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::USER_EVENTS));
  EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::SECURITY_EVENTS));
  EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::SEND_TAB_TO_SELF));
  EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::SHARING_MESSAGE));
  EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::SEND_TAB_TO_SELF));
  EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::HISTORY));
}

IN_PROC_BROWSER_TEST_F(LocalSyncTest, ShouldHonorSelectedTypes) {
  SyncServiceImpl* service =
      SyncServiceFactory::GetAsSyncServiceImplForProfileForTesting(
          browser()->GetProfile());

  // Wait until the first sync cycle is completed.
  ASSERT_TRUE(SyncTransportActiveChecker(service).Wait());

  ASSERT_TRUE(service->IsLocalSyncEnabled());
  ASSERT_FALSE(service->IsSyncFeatureEnabled());
  ASSERT_TRUE(service->GetActiveDataTypes().Has(syncer::BOOKMARKS));
  ASSERT_TRUE(service->GetActiveDataTypes().Has(syncer::PASSWORDS));

  service->GetUserSettings()->SetSelectedTypes(
      /*sync_everything=*/false, {syncer::UserSelectableType::kPasswords});

  ASSERT_TRUE(SyncTransportActiveChecker(service).Wait());

  EXPECT_TRUE(service->GetActiveDataTypes().Has(syncer::PASSWORDS));
  EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::BOOKMARKS));
}

// Setting up a custom passphrase is arguably meaningless for local sync, but it
// has been allowed historically.
IN_PROC_BROWSER_TEST_F(LocalSyncTest, ShouldSupportCustomPassphrase) {
  SyncServiceImpl* service =
      SyncServiceFactory::GetAsSyncServiceImplForProfileForTesting(
          browser()->GetProfile());

  // Wait until the first sync cycle is completed.
  ASSERT_TRUE(SyncTransportActiveChecker(service).Wait());

  ASSERT_TRUE(service->IsLocalSyncEnabled());
  ASSERT_FALSE(service->IsSyncFeatureEnabled());

  service->GetUserSettings()->SetEncryptionPassphrase(kTestPassphrase);

  EXPECT_TRUE(PassphraseAcceptedChecker(service).Wait());
}

IN_PROC_BROWSER_TEST_F(LocalSyncTest, ShouldReportNoLocalOnlyData) {
  SyncServiceImpl* service =
      SyncServiceFactory::GetAsSyncServiceImplForProfileForTesting(
          browser()->GetProfile());

  // Wait until the first sync cycle is completed.
  ASSERT_TRUE(SyncTransportActiveChecker(service).Wait());
  ASSERT_TRUE(service->IsLocalSyncEnabled());
  ASSERT_FALSE(service->HasSyncConsent());

  base::test::TestFuture<absl::flat_hash_map<syncer::DataType, size_t>>
      types_with_unsynced_data;
  service->GetTypesWithUnsyncedData({syncer::BOOKMARKS},
                                    types_with_unsynced_data.GetCallback());
  EXPECT_TRUE(types_with_unsynced_data.Get().empty());

  base::test::TestFuture<
      std::map<syncer::DataType, syncer::LocalDataDescription>>
      descriptions;
  service->GetLocalDataDescriptions({syncer::BOOKMARKS},
                                    descriptions.GetCallback());
  EXPECT_TRUE(descriptions.Get().empty());
}

}  // namespace

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
