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

#include "net/disk_cache/sql/sql_shared_cache.h"

#include <algorithm>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/hash.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/state_transitions.h"
#include "base/task/sequenced_task_runner.h"
#include "net/base/features.h"
#include "net/disk_cache/backend_cleanup_tracker.h"
#include "net/disk_cache/sql/shared_cache_client_remote.h"
#include "net/disk_cache/sql/sql_persistent_store.h"
#include "net/disk_cache/sql/sql_shared_cache_handle.h"
#include "net/disk_cache/sql/sql_shared_cache_isolated_database.h"
#include "net/http/http_cache.h"
#include "net/http/http_response_info.h"

namespace disk_cache {

SqlSharedCache::SqlSharedCache(
    std::string nik_string,
    SqlPersistentStore& store,
    const base::FilePath& directory,
    base::RepeatingCallback<void(SqlSharedCache&)> on_unreferenced_callback,
    scoped_refptr<base::SequencedTaskRunner> db_task_runner,
    scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor,
    scoped_refptr<BackendCleanupTracker> cleanup_tracker)
    : nik_string_(std::move(nik_string)),
      store_(store),
      directory_(directory),
      on_unreferenced_callback_(std::move(on_unreferenced_callback)),
      db_task_runner_(std::move(db_task_runner)),
      read_cache_memory_monitor_(std::move(read_cache_memory_monitor)),
      cleanup_tracker_(std::move(cleanup_tracker)) {}

SqlSharedCache::~SqlSharedCache() {
  isolated_database_.Reset();
  if (cleanup_tracker_) {
    CHECK(db_task_runner_);
    db_task_runner_->PostTaskAndReply(
        FROM_HERE, base::DoNothing(),
        base::DoNothingWithBoundArgs(std::move(cleanup_tracker_)));
  }
}

void SqlSharedCache::Cleanup(base::OnceClosure callback) {
  if (!isolated_database_) {
    std::move(callback).Run();
    return;
  }
  isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Cleanup)
      .Then(std::move(callback));
  isolated_database_.Reset();
}

void SqlSharedCache::InitIsolatedDatabase(
    SqlSharedCacheDbId shared_cache_db_id,
    base::OnceCallback<void(bool)> callback) {
  CHECK(!shared_cache_db_id_);
  shared_cache_db_id_ = shared_cache_db_id;
  isolated_database_ = SqlTrackedSequenceBound<SqlSharedCacheIsolatedDatabase>(
      db_task_runner_, store_->GetAsyncTaskManager(), nik_string_, directory_,
      shared_cache_db_id, db_task_runner_, read_cache_memory_monitor_);
  isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Init)
      .Then(base::BindOnce(
          [](base::OnceCallback<void(bool)> callback,
             base::expected<void, SqlSharedCacheIsolatedDatabase::Error>
                 result) { std::move(callback).Run(result.has_value()); },
          std::move(callback)));

  isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::GetAllUrlHashes)
      .Then(base::BindOnce(&SqlSharedCache::OnHashesLoaded,
                           weak_factory_.GetWeakPtr()));

  for (ClientsMap::iterator it(&clients_); !it.IsAtEnd(); it.Advance()) {
    isolated_database_
        .AsyncCall(&SqlSharedCacheIsolatedDatabase::GetSharedReadOnlyConnection)
        .Then(base::BindOnce(&SqlSharedCache::OnPendingFileSetForClient,
                             weak_factory_.GetWeakPtr(), it.GetCurrentKey()));
  }
}

void SqlSharedCache::RegisterClient(
    std::unique_ptr<SharedCacheClientRemote> client) {
  CHECK(client);
  auto* client_ptr = client.get();
  auto client_id = clients_.Add(std::move(client));
  client_ptr->SetDisconnectHandler(
      base::BindOnce(&SqlSharedCache::OnClientDisconnected,
                     weak_factory_.GetWeakPtr(), client_id, CreateHandle()));

  if (cached_hashes_.has_value()) {
    std::vector<uint32_t> hashes(cached_hashes_->begin(),
                                 cached_hashes_->end());
    client_ptr->OnResourcesAdded(hashes);
  }

  if (isolated_database_) {
    isolated_database_
        .AsyncCall(&SqlSharedCacheIsolatedDatabase::GetSharedReadOnlyConnection)
        .Then(base::BindOnce(&SqlSharedCache::OnPendingFileSetForClient,
                             weak_factory_.GetWeakPtr(), client_id));
  }
}

void SqlSharedCache::OnPendingFileSetForClient(
    ClientId client_id,
    base::expected<sqlite_vfs::PendingFileSet,
                   SqlSharedCacheIsolatedDatabase::Error> pending_file_set) {
  if (!pending_file_set.has_value()) {
    return;
  }
  auto* client = clients_.Lookup(client_id);
  if (!client) {
    return;
  }
  client->Initialize(std::move(*pending_file_set));
}

void SqlSharedCache::OnHashesLoaded(
    base::expected<std::vector<uint32_t>, SqlSharedCacheIsolatedDatabase::Error>
        hashes) {
  if (!cached_hashes_.has_value()) {
    cached_hashes_ = absl::flat_hash_set<uint32_t>();
  }
  if (!hashes.has_value()) {
    return;
  }
  for (auto hash : *hashes) {
    cached_hashes_->insert(hash);
  }

  if (!hashes->empty()) {
    for (ClientsMap::iterator it(&clients_); !it.IsAtEnd(); it.Advance()) {
      it.GetCurrentValue()->OnResourcesAdded(*hashes);
    }
  }
}
void SqlSharedCache::OnClientDisconnected(
    ClientId client_id,
    scoped_refptr<SqlSharedCacheHandle> handle) {
  clients_.Remove(client_id);
}

scoped_refptr<SqlSharedCacheHandle> SqlSharedCache::CreateHandle() {
  return base::MakeRefCounted<SqlSharedCacheHandle>(
      weak_factory_.GetWeakPtr(), base::PassKey<SqlSharedCache>());
}

void SqlSharedCache::IncrementHandleCount(base::PassKey<SqlSharedCacheHandle>) {
  handle_count_++;
}

void SqlSharedCache::DecrementHandleCount(base::PassKey<SqlSharedCacheHandle>) {
  handle_count_--;
  if (!IsReferenced()) {
    on_unreferenced_callback_.Run(*this);
  }
}

void SqlSharedCache::CopyEntries(
    base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries,
    scoped_refptr<base::RefCountedData<std::atomic_bool>> abort_flag,
    base::OnceCallback<void(
        base::queue<SqlPersistentStore::SharedCacheEligibleEntry>)> callback,
    base::RepeatingCallback<void(const CacheEntryKey&)>
        on_entry_copied_callback) {
  CHECK(pending_copy_entries_.empty());
  CHECK(!copy_callback_);
  CHECK(!current_copy_row_id_);
  CHECK(!on_entry_copied_callback_);
  CHECK(!entries.empty());
  CHECK(shared_cache_db_id_);
  CHECK(isolated_database_);
  pending_copy_entries_ = std::move(entries);
  copy_abort_flag_ = std::move(abort_flag);
  copy_callback_ = std::move(callback);
  on_entry_copied_callback_ = std::move(on_entry_copied_callback);
  CopyNextEntry();
}

void SqlSharedCache::CopyNextEntry() {
  if (pending_copy_entries_.empty() ||
      (copy_abort_flag_ &&
       copy_abort_flag_->data.load(std::memory_order_relaxed))) {
    FinishCopy();
    return;
  }
  auto entry = std::move(pending_copy_entries_.front());
  pending_copy_entries_.pop();
  current_entry_hash_ = base::PersistentHash(entry.url.spec());
  const auto key = entry.key;
  store_->OpenEntry(
      key, base::BindOnce(&SqlSharedCache::OnEntryOpenedForSharedCache,
                          weak_factory_.GetWeakPtr(), std::move(entry)));
}

void SqlSharedCache::OnEntryOpenedForSharedCache(
    SqlPersistentStore::SharedCacheEligibleEntry entry,
    SqlPersistentStore::EntryInfoOrError result) {
  if (!result.has_value() || !result->head) {
    OnCopyEntryFailed();
    return;
  }
  auto info = std::move(*result);
  if (info.body_end >
      net::features::kSqlDiskCacheMaxSharedCacheCopyEntrySize.Get()) {
    OnCopyEntryFailed();
    return;
  }
  net::HttpResponseInfo disk_response_info;
  bool response_truncated = false;
  if (!net::HttpCache::ParseResponseInfo(base::as_bytes(info.head->span()),
                                         &disk_response_info,
                                         &response_truncated) ||
      response_truncated ||
      disk_response_info.response_time != entry.response_info->response_time) {
    OnCopyEntryFailed();
    return;
  }
  auto pickled_buffer = base::MakeRefCounted<net::PickledIOBuffer>(
      entry.response_info->MakePickle(/*skip_transient_headers=*/true,
                                      /*response_truncated=*/false));
  if (info.body_end > 0) {
    int64_t chunk_size = std::min(
        static_cast<int64_t>(
            net::features::kSqlDiskCacheSharedCacheReadBufferSize.Get()),
        info.body_end);
    auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(chunk_size);
    const auto key = entry.key;
    const auto res_id = info.res_id;
    const auto body_end = info.body_end;
    store_->ReadEntryData(
        key, res_id, 0, buffer, chunk_size, body_end,
        /*sparse_reading=*/false,
        base::BindOnce(&SqlSharedCache::OnEntryDataReadForInsert,
                       weak_factory_.GetWeakPtr(), std::move(entry), res_id,
                       std::move(pickled_buffer), body_end, buffer));
    return;
  }
  isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Insert)
      .WithArgs(entry.key, std::move(pickled_buffer), info.body_end, nullptr)
      .Then(base::BindOnce(&SqlSharedCache::OnIsolatedDatabaseInserted,
                           weak_factory_.GetWeakPtr(), entry.key, info.res_id,
                           info.body_end, 0));
}

void SqlSharedCache::OnEntryDataReadForInsert(
    SqlPersistentStore::SharedCacheEligibleEntry entry,
    SqlPersistentStore::ResId res_id,
    scoped_refptr<net::PickledIOBuffer> headers,
    int64_t body_end,
    scoped_refptr<net::IOBuffer> buffer,
    base::expected<SqlPersistentStore::ReadResult, SqlPersistentStore::Error>
        result) {
  // For `sparse_reading = false`, `SqlPersistentStoreBackend::ReadEntryData`
  // zero-fills gaps and returns `read_bytes` equal to the requested size
  // as long as the read is within bounds.
  if (!result.has_value() ||
      result.value().read_bytes != static_cast<int>(buffer->size())) {
    OnCopyEntryFailed();
    return;
  }
  isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Insert)
      .WithArgs(entry.key, std::move(headers), body_end, buffer)
      .Then(base::BindOnce(&SqlSharedCache::OnIsolatedDatabaseInserted,
                           weak_factory_.GetWeakPtr(), entry.key, res_id,
                           body_end, buffer->size()));
}

void SqlSharedCache::OnIsolatedDatabaseInserted(
    CacheEntryKey key,
    SqlPersistentStore::ResId res_id,
    int64_t body_end,
    int64_t offset,
    base::expected<SqlSharedCacheRowId, SqlSharedCacheIsolatedDatabase::Error>
        result) {
  if (!result.has_value()) {
    OnCopyEntryFailed();
    return;
  }
  CHECK(!current_copy_row_id_);
  current_copy_row_id_ = result.value();
  ReadNextChunk(std::move(key), res_id, body_end, offset, result.value());
}

void SqlSharedCache::ReadNextChunk(CacheEntryKey key,
                                   SqlPersistentStore::ResId res_id,
                                   int64_t body_end,
                                   int64_t offset,
                                   SqlSharedCacheRowId shared_cache_row_id) {
  CHECK_LE(offset, body_end);
  if (offset == body_end) {
    MoveBlobsToSharedCache(key, res_id, shared_cache_row_id);
    return;
  }
  int64_t chunk_size =
      std::min(static_cast<int64_t>(
                   net::features::kSqlDiskCacheSharedCacheReadBufferSize.Get()),
               body_end - offset);
  auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(chunk_size);
  store_->ReadEntryData(
      key, res_id, offset, buffer, chunk_size, body_end,
      /*sparse_reading=*/false,
      base::BindOnce(&SqlSharedCache::OnEntryDataRead,
                     weak_factory_.GetWeakPtr(), key, res_id, body_end, offset,
                     shared_cache_row_id, buffer));
}

void SqlSharedCache::OnEntryDataRead(
    CacheEntryKey key,
    SqlPersistentStore::ResId res_id,
    int64_t body_end,
    int64_t offset,
    SqlSharedCacheRowId shared_cache_row_id,
    scoped_refptr<net::IOBuffer> buffer,
    base::expected<SqlPersistentStore::ReadResult, SqlPersistentStore::Error>
        result) {
  // For `sparse_reading = false`, `SqlPersistentStoreBackend::ReadEntryData`
  // zero-fills gaps and returns `read_bytes` equal to the requested size
  // as long as the read is within bounds.
  if (!result.has_value() || result.value().read_bytes != buffer->size()) {
    OnCopyEntryFailed();
    return;
  }
  bool set_ready = (offset + buffer->size() == body_end);
  isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::WriteBody)
      .WithArgs(key, shared_cache_row_id, offset, buffer, set_ready)
      .Then(base::BindOnce(&SqlSharedCache::OnIsolatedDatabaseWritten,
                           weak_factory_.GetWeakPtr(), key, res_id, body_end,
                           offset + buffer->size(), shared_cache_row_id));
}

void SqlSharedCache::OnIsolatedDatabaseWritten(
    CacheEntryKey key,
    SqlPersistentStore::ResId res_id,
    int64_t body_end,
    int64_t next_offset,
    SqlSharedCacheRowId shared_cache_row_id,
    base::expected<void, SqlSharedCacheIsolatedDatabase::Error> result) {
  if (!result.has_value()) {
    OnCopyEntryFailed();
    return;
  }
  ReadNextChunk(std::move(key), res_id, body_end, next_offset,
                shared_cache_row_id);
}

void SqlSharedCache::MoveBlobsToSharedCache(
    CacheEntryKey key,
    SqlPersistentStore::ResId res_id,
    SqlSharedCacheRowId shared_cache_row_id) {
  store_->MoveBlobsToSharedCache(
      key, res_id, {*shared_cache_db_id_, shared_cache_row_id},
      base::BindOnce(
          [](base::WeakPtr<SqlSharedCache> self, CacheEntryKey key,
             SqlPersistentStore::Error error) {
            if (self) {
              if (error == SqlPersistentStore::Error::kOk) {
                self->OnCopyEntryComplete(key);
              } else {
                self->OnCopyEntryFailed();
              }
            }
          },
          weak_factory_.GetWeakPtr(), key));
}

void SqlSharedCache::OnCopyEntryComplete(const CacheEntryKey& key) {
  CHECK(current_entry_hash_);
  // `cached_hashes_` is guaranteed to be populated by `OnHashesLoaded` which
  // was scheduled on the database task runner during `InitIsolatedDatabase`
  // prior to any entry copy operations on the same sequence.
  CHECK(cached_hashes_.has_value());
  if (cached_hashes_->insert(*current_entry_hash_).second) {
    copy_new_hashes_.push_back(*current_entry_hash_);
  }
  current_entry_hash_.reset();
  current_copy_row_id_ = std::nullopt;
  if (on_entry_copied_callback_) {
    on_entry_copied_callback_.Run(key);
  }
  CopyNextEntry();
}

void SqlSharedCache::OnCopyEntryFailed() {
  if (current_copy_row_id_.has_value()) {
    isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::DeleteEntry)
        .WithArgs(*current_copy_row_id_);
    current_copy_row_id_ = std::nullopt;
  }
  current_entry_hash_.reset();
  CopyNextEntry();
}

void SqlSharedCache::FinishCopy() {
  if (!copy_new_hashes_.empty()) {
    for (ClientsMap::iterator it(&clients_); !it.IsAtEnd(); it.Advance()) {
      it.GetCurrentValue()->OnResourcesAdded(copy_new_hashes_);
    }
    copy_new_hashes_.clear();
  }

  CHECK(copy_callback_);
  CHECK(!current_copy_row_id_);
  on_entry_copied_callback_.Reset();
  auto unprocessed_entries = std::move(pending_copy_entries_);
  CHECK(pending_copy_entries_.empty());
  copy_abort_flag_ = nullptr;
  auto callback = std::move(copy_callback_);
  CHECK(!copy_callback_);
  std::move(callback).Run(std::move(unprocessed_entries));
}

void SqlSharedCache::DeleteEntries(
    const std::vector<SqlSharedCacheRowId>& shared_cache_row_ids,
    base::OnceCallback<
        void(base::expected<void, SqlSharedCacheIsolatedDatabase::Error>)>
        callback) {
  if (!isolated_database_) {
    std::move(callback).Run(base::unexpected(
        SqlSharedCacheIsolatedDatabase::Error::kIsolatedDatabaseNotAvailable));
    return;
  }
  isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::DeleteEntries)
      .WithArgs(shared_cache_row_ids)
      .Then(std::move(callback));
}

void SqlSharedCache::Read(
    const CacheEntryKey& entry_key,
    SqlSharedCacheRowId shared_cache_row_id,
    int body_size,
    int64_t offset,
    scoped_refptr<net::IOBuffer> buffer,
    SqlPersistentStore::ReadResultOrErrorCallback callback) {
  if (!isolated_database_) {
    std::move(callback).Run(
        base::unexpected(SqlPersistentStore::Error::kNotFound));
    return;
  }
  if (offset > std::numeric_limits<int>::max()) {
    std::move(callback).Run(
        base::unexpected(SqlPersistentStore::Error::kFailedToExecute));
    return;
  }
  int offset_int = static_cast<int>(offset);
  isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::Read)
      .WithArgs(entry_key, shared_cache_row_id, body_size, offset_int, buffer)
      .Then(base::BindOnce(&SqlSharedCache::OnIsolatedDatabaseRead,
                           weak_factory_.GetWeakPtr(), std::move(callback)));
}

void SqlSharedCache::OnIsolatedDatabaseRead(
    SqlPersistentStore::ReadResultOrErrorCallback callback,
    SqlSharedCacheIsolatedDatabase::ReadResultOrError result) {
  if (result.has_value()) {
    std::move(callback).Run(result.value());
  } else {
    SqlPersistentStore::Error store_error;
    switch (result.error()) {
      case SqlSharedCacheIsolatedDatabase::Error::kEntryNotFound:
        store_error = SqlPersistentStore::Error::kNotFound;
        break;
      default:
        store_error = SqlPersistentStore::Error::kFailedToExecute;
        break;
    }
    std::move(callback).Run(base::unexpected(store_error));
  }
}

void SqlSharedCache::GetBlobHandle(
    const CacheEntryKey& entry_key,
    SqlSharedCacheRowId shared_cache_row_id,
    int body_size,
    base::OnceCallback<
        void(base::expected<scoped_refptr<SqlSharedCacheBlobHandle>,
                            SqlSharedCacheIsolatedDatabase::Error>)> callback) {
  if (!isolated_database_) {
    std::move(callback).Run(base::unexpected(
        SqlSharedCacheIsolatedDatabase::Error::kIsolatedDatabaseNotAvailable));
    return;
  }
  isolated_database_.AsyncCall(&SqlSharedCacheIsolatedDatabase::GetBlobHandle)
      .WithArgs(entry_key, shared_cache_row_id, body_size)
      .Then(std::move(callback));
}

}  // namespace disk_cache
