// Copyright 2016 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 <string>

#include "base/check.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/downgrade/snapshot_manager.h"

namespace downgrade {

namespace {

base::Version GetVersionFromFileName(const base::FilePath& path) {
#if BUILDFLAG(IS_WIN)
  // On Windows, for Unicode-aware applications, native pathnames are wchar_t
  // arrays encoded in UTF-16.
  return base::Version(base::WideToUTF8(path.BaseName().value()));
#elif BUILDFLAG(IS_POSIX)
  // On most platforms, native pathnames are char arrays, and the encoding
  // may or may not be specified.  On Mac OS X, native pathnames are encoded
  // in UTF-8.
  return base::Version(path.BaseName().value());
#endif  // BUILDFLAG(IS_WIN)
}

bool IsValidSnapshotDirectory(const base::FilePath& path) {
  base::Version snapshot_version = GetVersionFromFileName(path);
  return snapshot_version.IsValid() &&
         base::PathExists(path.Append(kDowngradeLastVersionFile));
}

}  // namespace

const base::FilePath::StringViewType kDowngradeLastVersionFile(
    FILE_PATH_LITERAL("Last Version"));
const base::FilePath::StringViewType kDowngradeDeleteSuffix(
    FILE_PATH_LITERAL(".CHROME_DELETE"));

const base::FilePath::StringViewType kSnapshotsDir(
    FILE_PATH_LITERAL("Snapshots"));

base::FilePath GetLastVersionFile(const base::FilePath& user_data_dir) {
  DCHECK(!user_data_dir.empty());
  return user_data_dir.Append(kDowngradeLastVersionFile);
}

std::optional<base::Version> GetLastVersion(
    const base::FilePath& user_data_dir) {
  DCHECK(!user_data_dir.empty());
  std::string last_version_str;
  if (base::ReadFileToString(GetLastVersionFile(user_data_dir),
                             &last_version_str)) {
    base::Version version(
        base::TrimWhitespaceASCII(last_version_str, base::TRIM_ALL));
    if (version.IsValid())
      return version;
  }
  return std::nullopt;
}

base::flat_set<base::Version> GetAvailableSnapshots(
    const base::FilePath& snapshot_dir) {
  std::vector<base::Version> result;
  base::FileEnumerator enumerator(snapshot_dir, false,
                                  base::FileEnumerator::DIRECTORIES);
  for (base::FilePath path = enumerator.Next(); !path.empty();
       path = enumerator.Next()) {
    base::Version snapshot_version = GetVersionFromFileName(path);
    if (!snapshot_version.IsValid() ||
        !base::PathExists(path.Append(kDowngradeLastVersionFile))) {
      continue;
    }
    result.push_back(std::move(snapshot_version));
  }
  return base::flat_set<base::Version>(std::move(result));
}

std::vector<base::FilePath> GetInvalidSnapshots(
    const base::FilePath& snapshot_dir) {
  std::vector<base::FilePath> result;
  base::FileEnumerator enumerator(snapshot_dir, false,
                                  base::FileEnumerator::DIRECTORIES);
  for (base::FilePath path = enumerator.Next(); !path.empty();
       path = enumerator.Next()) {
    if (!IsValidSnapshotDirectory(path))
      result.push_back(std::move(path));
  }
  return result;
}

std::optional<base::Version> GetSnapshotToRestore(
    const base::Version& version,
    const base::FilePath& user_data_dir) {
  DCHECK(version.IsValid());
  base::FilePath top_snapshot_dir = user_data_dir.Append(kSnapshotsDir);
  auto available_snapshots = GetAvailableSnapshots(top_snapshot_dir);

  auto upper_bound = available_snapshots.upper_bound(version);
  if (upper_bound != available_snapshots.begin())
    return *--upper_bound;
  return std::nullopt;
}

void RemoveDataForProfile(
    base::Time delete_begin,
    const base::FilePath& profile_path,
    std::optional<std::vector<base::FilePath>> files_to_delete) {
  SnapshotManager snapshot_manager(profile_path.DirName(), nullptr);
  snapshot_manager.DeleteSnapshotDataForProfile(
      delete_begin, profile_path.BaseName(), std::move(files_to_delete));
}

}  // namespace downgrade
