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

#ifndef NET_HTTP_NO_VARY_SEARCH_CACHE_STORAGE_H_
#define NET_HTTP_NO_VARY_SEARCH_CACHE_STORAGE_H_

#include <stddef.h>
#include <stdint.h>

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

#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "net/base/net_export.h"
#include "net/http/no_vary_search_cache.h"

namespace net {

class NoVarySearchCache;
class NoVarySearchCacheStorageFileOperations;

// An implementation of persistence for NoVarySearchCache. Disk operations are
// performed in the background on a thread pool.
class NET_EXPORT_PRIVATE NoVarySearchCacheStorage final
    : public NoVarySearchCache::Journal {
 public:
  enum class LoadFailed { kCannotJournal };

  using LoadResult =
      base::expected<std::unique_ptr<NoVarySearchCache>, LoadFailed>;

  using LoadCallback = base::OnceCallback<void(LoadResult)>;

  // Exposed for testing.
  static constexpr std::string_view kSnapshotFilename = "snapshot.baf";
  static constexpr std::string_view kJournalFilename = "journal.baj";

  // Generated by the command:
  //   echo "snapshot.baf version 2" | md5sum | cut -b 1-8
  static constexpr uint32_t kSnapshotMagicNumber = 0x62f965ce;

  // Generated by the command:
  //   echo "journal.baj version 2" | md5sum | cut -b 1-8
  static constexpr uint32_t kJournalMagicNumber = 0x24467e06;

  // Max file size. We won't try to load a file larger than this. We don't
  // expect the database to ever get this big.
  static constexpr size_t kMaxFileSize = 100 * 1024 * 1024;

  // Avoid automatically taking a new snapshot until the journal gets this big,
  // even if the snapshot is smaller.
  static constexpr size_t kMinimumAutoSnapshotSize = 64 * 1024;

  // Create a NoVarySearchCacheStorage object to persist `cache`. Load() must be
  // called in order for it to actually do anything.
  NoVarySearchCacheStorage();

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

  ~NoVarySearchCacheStorage() override;

  // Asynchronously loads a previously persisted NoVarySearchCache object using
  // `file_operations`, then starts journalling to a new journal file. If an
  // existing persisted cache could not be loaded, creates an empty
  // NoVarySearchCache object with `max_size` configured to be
  // `default_max_size`. If a new journal file could not be created, `callback`
  // is called with base::unexpected(kCannotJournal). Otherwise it is called
  // with a NoVarySearchCache object wrapped in a std::unique_ptr. `callback`
  // should merge in any entries that were added to the temporary cache used
  // during loading, and then switch to using the new cache. Any inserts or
  // erasures performed on the new cache will be journalled to disk. The new
  // NoVarySearchCache object should not be destroyed before this object is.
  // `callback` is never called synchronously.
  void Load(
      std::unique_ptr<NoVarySearchCacheStorageFileOperations> file_operations,
      size_t default_max_size,
      LoadCallback callback);

  // Serializes and persists the current state of `cache_` to disk, and starts a
  // new empty journal. Does nothing if the initial load hasn't completed yet.
  // This method is also called automatically any time the journal file grows
  // too large.
  void TakeSnapshot();

  // Implementation of NoVarySearchCache::Journal.

  // Serializes the arguments and posts a task to `journal_` to append the
  // result to the journal.
  void OnInsert(const std::string& partition_key,
                const std::string& base_url,
                const HttpNoVarySearchData& nvs_data,
                const std::optional<std::string>& query,
                base::Time update_time) override;

  // Serializes the arguments and posts a task to `journal_` to append to the
  // journal.
  void OnErase(const std::string& partition_key,
               const std::string& base_url,
               const HttpNoVarySearchData& nvs_data,
               const std::optional<std::string>& query) override;

  bool IsJournallingForTesting() const { return static_cast<bool>(journal_); }

 private:
  class Journaller;
  class Loader;

  using JournallerPtr = base::SequenceBound<Journaller>;

  // On successful creation of the Journaller on the background thread, a
  // pointer to it needs to be passed back to the
  // NoVarySearchCacheStorage::OnLoadComplete() method so that it can be called
  // and have its lifetime managed. We also need the loaded NoVarySearchCache
  // object.
  struct CacheAndJournalPointers {
    CacheAndJournalPointers(std::unique_ptr<NoVarySearchCache> cache,
                            JournallerPtr journal);

    CacheAndJournalPointers(const CacheAndJournalPointers&) = delete;

    // Move construction is allowed.
    CacheAndJournalPointers(CacheAndJournalPointers&&);

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

    ~CacheAndJournalPointers();

    std::unique_ptr<NoVarySearchCache> cache;
    JournallerPtr journal;
  };

  // Called when loading the persisted NoVarySearchCache has succeeded or
  // failed. Sets `cache_`, registers this object as a Journal for it, and
  // assigns to `journal_` on success. Calls `callback`.
  void OnLoadComplete(
      LoadCallback callback,
      base::expected<CacheAndJournalPointers, LoadFailed> result);

  // Called when `journal_` has found that journalling failed. Clears `journal_`
  // and unregisters this object as an observer.
  void OnJournallingFailed();

  // Appends `pickle` to the journal file.
  void AppendToJournal(base::Pickle pickle);

  raw_ptr<NoVarySearchCache> cache_ = nullptr;

  // Task runner for file operations.
  scoped_refptr<base::SequencedTaskRunner> background_task_runner_;

  // `journal_` lives on the `background_task_runner_`.
  JournallerPtr journal_;

  // Time when Start() was called.
  base::Time start_time_;

  base::WeakPtrFactory<NoVarySearchCacheStorage> weak_factory_{this};
};

}  // namespace net

#endif  // NET_HTTP_NO_VARY_SEARCH_CACHE_STORAGE_H_
