// 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/downgrade/user_data_downgrade.h"

#include <optional>

#include "base/containers/flat_set.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
#include "chrome/browser/downgrade/snapshot_file_collector.h"
#include "chrome/common/chrome_constants.h"
#include "content/public/browser/browsing_data_remover.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace downgrade {

TEST(UserDataDowngradeTests, GetInvalidSnapshots) {
  base::ScopedTempDir snapshot_dir;
  ASSERT_TRUE(snapshot_dir.CreateUniqueTempDir());
  base::FilePath snapshot_path = snapshot_dir.GetPath();
  for (const std::string& name : std::vector<std::string>{"10", "11", "30"}) {
    ASSERT_TRUE(base::CreateDirectory(snapshot_path.AppendASCII(name)));
    base::File(
        snapshot_path.AppendASCII(name).Append(kDowngradeLastVersionFile),
        base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  }
  ASSERT_TRUE(base::CreateDirectory(snapshot_path.AppendASCII("20")));
  ASSERT_TRUE(base::CreateDirectory(snapshot_path.AppendASCII("Snapshot 20")));
  base::File(snapshot_path.AppendASCII("Snapshot 20")
                 .Append(kDowngradeLastVersionFile),
             base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  ASSERT_TRUE(base::CreateDirectory(snapshot_path.AppendASCII("Something")));
  auto snapshots = GetInvalidSnapshots(snapshot_path);
  base::flat_set<base::FilePath> expected{
      snapshot_path.AppendASCII("Snapshot 20"), snapshot_path.AppendASCII("20"),
      snapshot_path.AppendASCII("Something")};
  EXPECT_EQ(base::flat_set<base::FilePath>(snapshots), expected);
}

TEST(UserDataDowngradeTests, GetAvailableSnapshots) {
  base::ScopedTempDir snapshot_dir;
  ASSERT_TRUE(snapshot_dir.CreateUniqueTempDir());

  for (const std::string& name :
       std::vector<std::string>{"8", "10.0.0", "11.0.11.123", "30.0.1.1"}) {
    ASSERT_TRUE(
        base::CreateDirectory(snapshot_dir.GetPath().AppendASCII(name)));
    base::File(snapshot_dir.GetPath().AppendASCII(name).Append(
                   kDowngradeLastVersionFile),
               base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  }
  ASSERT_TRUE(
      base::CreateDirectory(snapshot_dir.GetPath().AppendASCII("20.0.2")));
  ASSERT_TRUE(
      base::CreateDirectory(snapshot_dir.GetPath().AppendASCII("Snapshot 20")));
  ASSERT_TRUE(
      base::CreateDirectory(snapshot_dir.GetPath().AppendASCII("Something")));
  base::File(snapshot_dir.GetPath().AppendASCII("9"),
             base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  auto snapshots = GetAvailableSnapshots(snapshot_dir.GetPath());
  base::flat_set<base::Version> expected{
      base::Version("8"), base::Version("10.0.0"), base::Version("11.0.11.123"),
      base::Version("30.0.1.1")};
  EXPECT_EQ(snapshots, expected);
}

TEST(UserDataDowngradeTests, GetSnapshotToRestore) {
  base::ScopedTempDir user_data_dir;
  ASSERT_TRUE(user_data_dir.CreateUniqueTempDir());
  const base::FilePath snapshot_dir =
      user_data_dir.GetPath().Append(kSnapshotsDir);
  ASSERT_TRUE(base::CreateDirectory(snapshot_dir.AppendASCII("9")));
  for (const std::string& name :
       std::vector<std::string>{"10.0.0", "11.3.2", "20.0.0", "30.0.0"}) {
    ASSERT_TRUE(base::CreateDirectory(snapshot_dir.AppendASCII(name)));
    base::File(snapshot_dir.AppendASCII(name).Append(kDowngradeLastVersionFile),
               base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  }

  EXPECT_EQ(GetSnapshotToRestore(base::Version("9"), user_data_dir.GetPath()),
            std::nullopt);
  EXPECT_EQ(
      *GetSnapshotToRestore(base::Version("10.1.0"), user_data_dir.GetPath()),
      base::Version("10.0.0"));
  EXPECT_EQ(
      *GetSnapshotToRestore(base::Version("15.5.3"), user_data_dir.GetPath()),
      base::Version("11.3.2"));
  EXPECT_EQ(
      *GetSnapshotToRestore(base::Version("30.0.0"), user_data_dir.GetPath()),
      base::Version("30.0.0"));
  EXPECT_EQ(
      *GetSnapshotToRestore(base::Version("31.0.0"), user_data_dir.GetPath()),
      base::Version("30.0.0"));
}

TEST(UserDataDowngradeTests, RemoveDataForProfile) {
  base::ScopedTempDir user_data_dir;
  ASSERT_TRUE(user_data_dir.CreateUniqueTempDir());
  const base::FilePath snapshot_dir =
      user_data_dir.GetPath().Append(kSnapshotsDir);
  const auto profile_path_default =
      user_data_dir.GetPath().AppendASCII("Default");
  const auto profile_path_1 = user_data_dir.GetPath().AppendASCII("Profile 1");
  const auto snapshot_profile_path_default =
      snapshot_dir.AppendASCII("1").AppendASCII("Default");
  const auto snapshot_profile_path_1 =
      snapshot_dir.AppendASCII("1").AppendASCII("Profile 1");
  ASSERT_TRUE(base::CreateDirectory(profile_path_default));
  ASSERT_TRUE(base::CreateDirectory(profile_path_1));
  ASSERT_TRUE(base::CreateDirectory(snapshot_profile_path_default));
  ASSERT_TRUE(base::CreateDirectory(snapshot_profile_path_1));
  base::File(profile_path_default.Append(kDowngradeLastVersionFile),
             base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  base::File(profile_path_1.Append(kDowngradeLastVersionFile),
             base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  base::File(snapshot_dir.AppendASCII("1").Append(kDowngradeLastVersionFile),
             base::File::FLAG_CREATE | base::File::FLAG_WRITE);

  const auto profile_items = CollectProfileItems();
  DCHECK(!profile_items.empty());
  for (const auto& item : profile_items) {
    if (item.is_directory) {
      ASSERT_TRUE(base::CreateDirectory(
          snapshot_profile_path_default.Append(item.path)));
      ASSERT_TRUE(
          base::CreateDirectory(snapshot_profile_path_1.Append(item.path)));
    } else {
      base::File(snapshot_profile_path_default.Append(item.path),
                 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
      base::File(snapshot_profile_path_1.Append(item.path),
                 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
    }
  }

  base::File::Info snapshot_info;
  ASSERT_TRUE(base::GetFileInfo(snapshot_dir, &snapshot_info));

  auto remove_data_for_profile = [](base::Time delete_begin,
                                    const base::FilePath& profile_path,
                                    uint64_t remove_mask) {
    bool delete_all =
        (((remove_mask & chrome_browsing_data_remover::WIPE_PROFILE) ==
          chrome_browsing_data_remover::WIPE_PROFILE) ||
         ((remove_mask & chrome_browsing_data_remover::ALL_DATA_TYPES) ==
          chrome_browsing_data_remover::ALL_DATA_TYPES)) &&
        delete_begin.is_null();
    std::optional<std::vector<base::FilePath>> files_to_delete;
    if (!delete_all) {
      files_to_delete.emplace();
      for (const auto& item : CollectProfileItems()) {
        if (item.data_types & remove_mask) {
          files_to_delete->push_back(item.path);
        }
      }
    }
    RemoveDataForProfile(delete_begin, profile_path,
                         std::move(files_to_delete));
  };

  // Nothing should be deleted from |profile_path_default| since delete_begin
  // is after the snapshot has been created.
  remove_data_for_profile(base::Time::Max(), profile_path_default,
                          chrome_browsing_data_remover::DATA_TYPE_BOOKMARKS);
  // Only the bookmarks should be deleted.
  remove_data_for_profile(base::Time::Min(), profile_path_1,
                          chrome_browsing_data_remover::DATA_TYPE_BOOKMARKS);
  EXPECT_TRUE(base::PathExists(
      snapshot_profile_path_default.Append(chrome::kPreferencesFilename)));
  EXPECT_TRUE(base::PathExists(
      snapshot_profile_path_1.Append(chrome::kPreferencesFilename)));
  EXPECT_TRUE(base::PathExists(snapshot_profile_path_default.Append(
      chrome::kSecurePreferencesFilename)));
  EXPECT_TRUE(base::PathExists(
      snapshot_profile_path_1.Append(chrome::kSecurePreferencesFilename)));

  for (const auto& item : profile_items) {
    EXPECT_TRUE(
        base::PathExists(snapshot_profile_path_default.Append(item.path)));
    EXPECT_EQ((item.data_types &
               chrome_browsing_data_remover::DATA_TYPE_BOOKMARKS) == 0ULL,
              base::PathExists(snapshot_profile_path_1.Append(item.path)));
  }

  const auto remove_mask =
      content::BrowsingDataRemover::DATA_TYPE_COOKIES |
      chrome_browsing_data_remover::DATA_TYPE_ISOLATED_ORIGINS |
      chrome_browsing_data_remover::DATA_TYPE_HISTORY |
      chrome_browsing_data_remover::DATA_TYPE_CONTENT_SETTINGS |
      chrome_browsing_data_remover::DATA_TYPE_PASSWORDS |
      chrome_browsing_data_remover::DATA_TYPE_FORM_DATA;

  // Delete some data from default profile.
  remove_data_for_profile(base::Time::Min(), profile_path_default, remove_mask);
  for (const auto& item : profile_items) {
    EXPECT_EQ(
        (item.data_types & remove_mask) == 0ULL,
        base::PathExists(snapshot_profile_path_default.Append(item.path)));
  }
  // Wipe profile 1
  remove_data_for_profile(base::Time(), profile_path_1,
                          chrome_browsing_data_remover::WIPE_PROFILE);
  EXPECT_FALSE(base::PathExists(snapshot_profile_path_1));
}

}  // namespace downgrade
