// Copyright 2017 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/safe_browsing/test_safe_browsing_database_helper.h"

#include <algorithm>
#include <utility>

#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
#include "chrome/browser/safe_browsing/v5_search_hashes_cache_factory.h"
#include "components/safe_browsing/core/browser/db/sb_database.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/core/browser/db/v4_test_util.h"
#include "components/safe_browsing/core/browser/db/v5_search_hashes_cache.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/security_interstitials/core/unsafe_resource.h"
#include "content/public/test/test_utils.h"

namespace {

// UI manager that never actually shows any interstitials, but emulates as if
// the user chose to proceed through them.
class FakeSafeBrowsingUIManager
    : public safe_browsing::TestSafeBrowsingUIManager {
 public:
  FakeSafeBrowsingUIManager() = default;

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

 protected:
  ~FakeSafeBrowsingUIManager() override = default;

  void DisplayBlockingPage(const UnsafeResource& resource) override {
    resource.DispatchCallback(FROM_HERE, true /* proceed */,
                              true /* showed_interstitial */,
                              false /* has_post_commit_interstitial_skipped */);
  }
};

}  // namespace

// TODO(crbug.com/362791941): Handle v4 references.
// This class automatically inserts lists into the store map when initializing
// the test database.
class InsertingDatabaseFactory : public safe_browsing::TestSBDatabaseFactory {
 public:
  explicit InsertingDatabaseFactory(
      safe_browsing::TestV4StoreFactory* store_factory,
      const std::vector<safe_browsing::ListIdentifier>& lists_to_insert)
      : lists_to_insert_(lists_to_insert), store_factory_(store_factory) {}

  std::unique_ptr<safe_browsing::SBDatabase, base::OnTaskRunnerDeleter> Create(
      const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
      std::unique_ptr<safe_browsing::StoreMap> store_map) override {
    const base::FilePath base_store_path(FILE_PATH_LITERAL("UrlDb.store"));
    for (const auto& id : lists_to_insert_) {
      if (!store_map->contains(id)) {
        const base::FilePath store_path = base::GetUniquePath(base_store_path);
        store_map->insert(
            {id, store_factory_->CreateV4Store(
                     db_task_runner,
                     store_path.empty() ? base_store_path : store_path,
                     /*v5_prefix_size=*/4,
                     /*is_eligible_for_migration=*/true,
                     /*is_extensions_blocklist=*/false)});
      }
    }

    for (const auto& it : *store_map)
      lists_.push_back(it.first);
    return safe_browsing::TestSBDatabaseFactory::Create(db_task_runner,
                                                        std::move(store_map));
  }

  const std::vector<safe_browsing::ListIdentifier> lists() { return lists_; }

 private:
  std::vector<safe_browsing::ListIdentifier> lists_to_insert_;
  std::vector<safe_browsing::ListIdentifier> lists_;
  raw_ptr<safe_browsing::TestV4StoreFactory> store_factory_;
};

TestSafeBrowsingDatabaseHelper::TestSafeBrowsingDatabaseHelper()
    : TestSafeBrowsingDatabaseHelper(
          std::make_unique<
              safe_browsing::TestV4GetHashProtocolManagerFactory>(),
          std::vector<safe_browsing::ListIdentifier>()) {}

TestSafeBrowsingDatabaseHelper::TestSafeBrowsingDatabaseHelper(
    std::unique_ptr<safe_browsing::TestV4GetHashProtocolManagerFactory>
        v4_get_hash_factory,
    std::vector<safe_browsing::ListIdentifier> lists_to_insert)
    : v4_get_hash_factory_(v4_get_hash_factory.get()) {
  sb_factory_ =
      std::make_unique<safe_browsing::TestSafeBrowsingServiceFactory>();
  sb_factory_->SetTestUIManager(new FakeSafeBrowsingUIManager());
  sb_factory_->UseSBLocalDatabaseManager();
  safe_browsing::SafeBrowsingService::RegisterFactory(sb_factory_.get());

  auto store_factory = std::make_unique<safe_browsing::TestV4StoreFactory>();
  auto sb_db_factory = std::make_unique<InsertingDatabaseFactory>(
      store_factory.get(), lists_to_insert);

  sb_db_factory_ = sb_db_factory.get();

  safe_browsing::SBDatabase::RegisterStoreFactoryForTest(
      std::move(store_factory));
  safe_browsing::SBDatabase::RegisterDatabaseFactoryForTest(
      std::move(sb_db_factory));

  if (v4_get_hash_factory) {
    safe_browsing::V4GetHashProtocolManager::RegisterFactory(
        std::move(v4_get_hash_factory));
  }
}

TestSafeBrowsingDatabaseHelper::~TestSafeBrowsingDatabaseHelper() {
  safe_browsing::V4GetHashProtocolManager::RegisterFactory(nullptr);
  safe_browsing::SBDatabase::RegisterDatabaseFactoryForTest(nullptr);
  safe_browsing::SBDatabase::RegisterStoreFactoryForTest(nullptr);
  safe_browsing::SafeBrowsingService::RegisterFactory(nullptr);
}

// TODO(crbug.com/372395685): Remove list_id (which can be derived from
// threat_type in V5) and threat_metadata when deprecating V4.
void TestSafeBrowsingDatabaseHelper::AddFullHashToDbAndFullHashCache(
    const GURL& bad_url,
    const safe_browsing::ListIdentifier& list_id,
    const safe_browsing::ThreatMetadata& threat_metadata,
    safe_browsing::V5::ThreatType threat_type,
    bool is_warn_only,
    Profile* profile) {
  LocallyMarkPrefixAsBad(bad_url, list_id);

  if (base::FeatureList::IsEnabled(safe_browsing::kLocalListsUseSBv5)) {
    safe_browsing::V5SearchHashesCacheFactory::GetForProfile(profile)
        ->CacheArtificialV5SearchHashesLookupVerdict(bad_url, threat_type,
                                                     is_warn_only);
    return;
  }

  // Should only be called if we are mocking the v4 hash factory.
  CHECK(v4_get_hash_factory_);

  safe_browsing::FullHashInfo full_hash_info =
      GetFullHashInfoWithMetadata(bad_url, list_id, threat_metadata);
  v4_get_hash_factory_->AddToFullHashCache(full_hash_info);
}

void TestSafeBrowsingDatabaseHelper::LocallyMarkPrefixAsBad(
    const GURL& url,
    const safe_browsing::ListIdentifier& list_id) {
  safe_browsing::FullHashStr full_hash =
      safe_browsing::SBProtocolManagerUtil::GetFullHash(url);
  while (!sb_db_factory_->IsReady()) {
    content::RunAllTasksUntilIdle();
  }
  sb_db_factory_->MarkPrefixAsBad(list_id, full_hash);
}

bool TestSafeBrowsingDatabaseHelper::HasListSynced(
    const safe_browsing::ListIdentifier& list_id) {
  return std::ranges::contains(sb_db_factory_->lists(), list_id);
}
