// Copyright 2026 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_DISK_CACHE_SQL_SQL_SHARED_CACHE_ISOLATED_DATABASE_H_
#define NET_DISK_CACHE_SQL_SQL_SHARED_CACHE_ISOLATED_DATABASE_H_

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

#include "base/files/file_path.h"
#include "base/memory/scoped_refptr.h"
#include "base/types/expected.h"
#include "base/types/pass_key.h"
#include "components/sqlite_vfs/sqlite_sandboxed_vfs.h"
#include "components/sqlite_vfs/vfs_utils.h"
#include "net/base/io_buffer.h"
#include "net/base/net_export.h"
#include "net/disk_cache/sql/cache_entry_key.h"
#include "net/disk_cache/sql/sql_backend_aliases.h"
#include "net/disk_cache/sql/sql_persistent_store.h"
#include "net/disk_cache/sql/sql_read_cache_memory_monitor.h"
#include "net/disk_cache/sql/sql_shared_cache_blob_handle.h"
#include "sql/database.h"
#include "sql/streaming_blob_handle.h"

namespace disk_cache {

class SqlSharedCacheIsolatedDatabaseTest;

// Manages a single SQLite database shard for a specific Network Isolation Key
// (NIK).
//
// To prevent cross-site data leaks, each NIK is assigned an isolated database
// via sqlite_vfs::SandboxedVfs. In the future, a read-only PendingFileSet
// corresponding to this database will be sent to the renderer process,
// allowing direct read access to the cached resources from the renderer.
// This class orchestrates the underlying SQLite database lifecycle, initializes
// the schema and metadata tables, and automatically razes and recreates the
// database upon NIK mismatch or schema version incompatibility.
class NET_EXPORT_PRIVATE SqlSharedCacheIsolatedDatabase {
 public:
  friend class SqlSharedCacheIsolatedDatabaseTest;

  enum class Error {
    kFailedForTesting = 1,
    kFailedToOpenVfsFileSet = 2,
    kFailedToOpenDatabase = 3,
    kFailedToRazeIncompatibleVersionDatabase = 4,
    kFailedToRazeIncompatibleNikDatabase = 5,
    kFailedToStartTransaction = 6,
    kFailedToCreateResourcesTable = 7,
    kFailedToCreateResourcesTableIndex = 8,
    kFailedToInitMetaTable = 9,
    kFailedToSetNikInMetaTable = 10,
    kFailedToCommitTransaction = 11,
    kDatabaseNotOpen = 12,
    kFailedToExecuteStatement = 13,
    kEntryNotFound = 14,
    kFailedToGetBlob = 15,
    kFailedToWriteBlob = 16,
    kFailedToSetReady = 17,
    kBodyTooLarge = 18,
    kInvalidWriteRange = 19,
    kInvalidReadRange = 20,
    kFailedToReadBlob = 21,
    kFailedToShareConnection = 22,
    kIsolatedDatabaseNotAvailable = 23,
    kBodySizeMismatch = 24,
  };

  using ReadResult = SqlPersistentStore::ReadResult;
  using ReadResultOrError = base::expected<ReadResult, Error>;

  SqlSharedCacheIsolatedDatabase(
      std::string nik_string,
      const base::FilePath& directory,
      SqlSharedCacheDbId shared_cache_db_id,
      scoped_refptr<base::SequencedTaskRunner> task_runner,
      scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor =
          nullptr);
  ~SqlSharedCacheIsolatedDatabase();

  // Returns the hashes of all ready resource URLs stored in this isolated
  // database.
  base::expected<std::vector<uint32_t>, Error> GetAllUrlHashes();

  // Returns a PendingFileSet that represents a read-only connection to the
  // database. This PendingFileSet can be passed to another process (like a
  // sandboxed renderer) to allow it to read from the database directly.
  base::expected<sqlite_vfs::PendingFileSet, Error>
  GetSharedReadOnlyConnection();

  base::expected<void, Error> Init();

  // Inserts a new resource with the serialized headers and total body size.
  // `total_body_size` is restricted by the maximum value of int32_t.
  // The entry is immediately marked as `ready` if `total_body_size` is 0, or if
  // `body` is provided and its size matches `total_body_size`.
  // Otherwise, the entry is not ready and subsequent calls to `WriteBody` are
  // required to append the body data. Finally, a call to `WriteBody` with
  // `set_ready = true` makes it `ready` and accessible via `Read`.
  base::expected<SqlSharedCacheRowId, Error> Insert(
      const CacheEntryKey& entry_key,
      scoped_refptr<net::IOBuffer> headers,
      uint32_t total_body_size,
      scoped_refptr<net::IOBuffer> body);

  // Appends data from `buffer` (at the given `offset`) to the entry's
  // body. If `set_ready` is true, the entry is marked as `ready` and will
  // become accessible to `Read`.
  base::expected<void, Error> WriteBody(const CacheEntryKey& entry_key,
                                        SqlSharedCacheRowId shared_cache_row_id,
                                        int offset,
                                        scoped_refptr<net::IOBuffer> buffer,
                                        bool set_ready);

  // Reads data from the entry's body into `buffer` starting at `offset`. The
  // total body size must be specified in `body_size` to validate that the read
  // range (`offset` + `buffer->size()`) does not exceed `body_size`. The entry
  // must be in the `ready` state, otherwise this operation will fail.
  ReadResultOrError Read(const CacheEntryKey& entry_key,
                         SqlSharedCacheRowId shared_cache_row_id,
                         int body_size,
                         int offset,
                         scoped_refptr<net::IOBuffer> buffer);

  // Returns a handle to the entry's body blob, allowing streaming reads.
  // The entry must be in the `ready` state, otherwise this operation will fail
  // and return an Error.
  base::expected<scoped_refptr<SqlSharedCacheBlobHandle>, Error> GetBlobHandle(
      const CacheEntryKey& entry_key,
      SqlSharedCacheRowId shared_cache_row_id,
      int body_size);

  // Deletes an entry by its row ID. Used for cleaning up partial entries.
  void DeleteEntry(SqlSharedCacheRowId shared_cache_row_id);

  // Deletes entries specified by `shared_cache_row_ids` from the database.
  base::expected<void, Error> DeleteEntries(
      const std::vector<SqlSharedCacheRowId>& shared_cache_row_ids);

  // Releases `db_assets_`.
  void Cleanup();

  enum class OperationForTesting {
    kInit,
    kInsert,
    kWriteBody,
    kRead,
    kDeleteEntry,
    kDeleteEntries,
  };

  using SimFailedCallback =
      base::RepeatingCallback<bool(OperationForTesting op)>;

  void SetSimulateDbFailureCallbackForTesting(SimFailedCallback callback);
  bool HasRowForTesting(SqlSharedCacheRowId shared_cache_row_id);

 private:
  class DatabaseAssets {
   public:
    static std::unique_ptr<DatabaseAssets> MaybeCreate(
        const base::FilePath& directory,
        SqlSharedCacheDbId shared_cache_db_id);

    DatabaseAssets(const base::FilePath& directory,
                   SqlSharedCacheDbId shared_cache_db_id,
                   sqlite_vfs::SqliteVfsFileSet vfs_file_set,
                   base::PassKey<DatabaseAssets>);
    DatabaseAssets(const DatabaseAssets&) = delete;
    DatabaseAssets& operator=(const DatabaseAssets&) = delete;

    ~DatabaseAssets();

    sql::Database& db() { return db_; }
    base::FilePath GetDbVirtualFilePath() const;
    base::expected<sqlite_vfs::PendingFileSet, sqlite_vfs::FileSetError>
    ShareConnection();
    void AbandonAndDeleteFiles();

   private:
    const base::FilePath directory_;
    const SqlSharedCacheDbId shared_cache_db_id_;
    sqlite_vfs::SqliteVfsFileSet vfs_file_set_;
    sqlite_vfs::SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner_;
    sql::Database db_;
  };

  class SqlSharedCacheBlobHandleImpl : public SqlSharedCacheBlobHandle {
   public:
    SqlSharedCacheBlobHandleImpl(
        base::OnceClosure decrement_closure,
        scoped_refptr<base::SequencedTaskRunner> task_runner);

   private:
    ~SqlSharedCacheBlobHandleImpl() override;

    base::OnceClosure decrement_closure_;
    scoped_refptr<base::SequencedTaskRunner> task_runner_;
  };

  class BlobHandleHolder {
   public:
    BlobHandleHolder(sql::StreamingBlobHandle blob_handle,
                     const CacheEntryKey& entry_key,
                     int body_size);
    ~BlobHandleHolder();

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

    sql::StreamingBlobHandle& blob_handle() { return blob_handle_; }

    void IncrementRefCount() { ++ref_count_; }
    void DecrementRefCount() { --ref_count_; }
    size_t ref_count() const { return ref_count_; }

    const std::string_view resource_url() const {
      return entry_key_.resource_url();
    }
    int body_size() const { return body_size_; }

   private:
    sql::StreamingBlobHandle blob_handle_;
    const CacheEntryKey entry_key_;
    const int body_size_;
    size_t ref_count_ = 1;
  };

  void DecrementBlobHandleRefCount(SqlSharedCacheRowId shared_cache_row_id);

  base::expected<BlobHandleHolder*, Error> GetCachedBlobHandleHolder(
      const CacheEntryKey& entry_key,
      SqlSharedCacheRowId shared_cache_row_id,
      int body_size);

  base::expected<sql::StreamingBlobHandle, Error> GetStreamingBlobHandle(
      const CacheEntryKey& entry_key,
      SqlSharedCacheRowId shared_cache_row_id,
      int body_size);

  base::expected<void, Error> WriteBodyInternal(
      const CacheEntryKey& entry_key,
      SqlSharedCacheRowId shared_cache_row_id,
      int offset,
      scoped_refptr<net::IOBuffer> buffer,
      bool set_ready,
      bool in_transaction);

  bool ShouldSimulateFailure(OperationForTesting op) const;

  std::string nik_string_;
  std::unique_ptr<DatabaseAssets> db_assets_;
  SimFailedCallback simulate_db_failure_callback_;
  scoped_refptr<base::SequencedTaskRunner> task_runner_;
  scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor_;

  absl::flat_hash_map<SqlSharedCacheRowId, std::unique_ptr<BlobHandleHolder>>
      blob_handle_holders_;

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

}  // namespace disk_cache

#endif  // NET_DISK_CACHE_SQL_SQL_SHARED_CACHE_ISOLATED_DATABASE_H_
