// Copyright 2018 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/browsing_data/browsing_data_history_observer_service.h"

#include <tuple>

#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
#include "chrome/browser/browsing_data/navigation_entry_remover.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/common/buildflags.h"
#include "components/search_engines/template_url_service.h"

#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/commerce/merchant_viewer/merchant_viewer_data_manager.h"
#include "chrome/browser/commerce/merchant_viewer/merchant_viewer_data_manager_factory.h"
#include "chrome/browser/commerce/shopping_service_factory.h"
#include "components/commerce/core/feature_utils.h"
#include "components/commerce/core/shopping_service.h"
#endif

#if BUILDFLAG(ENABLE_SESSION_SERVICE)
#include "chrome/browser/sessions/session_service_factory.h"
#endif

namespace {

// This method takes in parameter |urls|, which is a map,
// {Origin -> {Count, LastVisitTime}}, keyed by origin to the
// count of its URLs in the history and its last visit time.
// Here, we iterate over the map to return the set of origins
// which doesn't have any of its urls present in the history.
// A flat_set is returned for efficient lookups
// in the Contains method below. The insertion
// time is O(n.logn) as we invoke the move
// constructor with std::vector.
base::flat_set<GURL> GetDeletedOrigins(
    const history::OriginCountAndLastVisitMap& urls) {
  std::vector<GURL> deleted_origins;
  // Iterating over the map {Origin -> {Count, LastVisitTime}}.
  for (const auto& key_value : urls) {
    // If the Count is greater than 0, it means few of the origin's URL(s) are
    // still in the history, so we shouldn't mark the origin for deletion.
    if (key_value.second.first > 0)
      continue;
    deleted_origins.push_back(key_value.first);
  }
  return base::flat_set<GURL>(std::move(deleted_origins));
}

bool Contains(const base::flat_set<GURL>& deleted_origins,
              const GURL& template_gurl) {
  return deleted_origins.contains(template_gurl.DeprecatedGetOriginAsURL());
}

void DeleteTemplateUrlsForTimeRange(TemplateURLService* keywords_model,
                                    base::Time delete_begin,
                                    base::Time delete_end) {
  if (!keywords_model->loaded()) {
    // TODO(crbug.com/40211652): Ignoring the return value here is
    // probably a bug.
    std::ignore = keywords_model->RegisterOnLoadedCallback(
        base::BindOnce(&DeleteTemplateUrlsForTimeRange, keywords_model,
                       delete_begin, delete_end));
    keywords_model->Load();
    return;
  }

  keywords_model->RemoveAutoGeneratedBetween(delete_begin, delete_end);
}

void DeleteTemplateUrlsForDeletedOrigins(TemplateURLService* keywords_model,
                                         base::flat_set<GURL> deleted_origins) {
  if (!keywords_model->loaded()) {
    // TODO(crbug.com/40211652): Ignoring the return value here is
    // probably a bug.
    std::ignore = keywords_model->RegisterOnLoadedCallback(
        base::BindOnce(&DeleteTemplateUrlsForDeletedOrigins, keywords_model,
                       std::move(deleted_origins)));
    keywords_model->Load();
    return;
  }

  keywords_model->RemoveAutoGeneratedForUrlsBetween(
      base::BindRepeating(&Contains, std::move(deleted_origins)),
      base::Time::Min(), base::Time::Max());
}

#if BUILDFLAG(IS_ANDROID)
void ClearCommerceData(Profile* profile,
                       const history::DeletionInfo& deletion_info) {
  MerchantViewerDataManager* merchant_viewer_data_manager =
      MerchantViewerDataManagerFactory::GetForProfile(profile);
  if (!merchant_viewer_data_manager)
    return;
  if (deletion_info.time_range().IsValid()) {
    merchant_viewer_data_manager->DeleteMerchantViewerDataForTimeRange(
        deletion_info.time_range().begin(), deletion_info.time_range().end());
  } else {
    auto deleted_origins =
        GetDeletedOrigins(deletion_info.deleted_urls_origin_map());

    merchant_viewer_data_manager->DeleteMerchantViewerDataForOrigins(
        std::move(deleted_origins));
  }
}
#endif

}  // namespace

BrowsingDataHistoryObserverService::BrowsingDataHistoryObserverService(
    Profile* profile)
    : profile_(profile) {
  auto* history_service = HistoryServiceFactory::GetForProfile(
      profile, ServiceAccessType::EXPLICIT_ACCESS);
  if (history_service)
    history_observation_.Observe(history_service);
}

BrowsingDataHistoryObserverService::~BrowsingDataHistoryObserverService() =
    default;

void BrowsingDataHistoryObserverService::Shutdown() {
  history_observation_.Reset();
}

void BrowsingDataHistoryObserverService::OnHistoryDeletions(
    history::HistoryService* history_service,
    const history::DeletionInfo& deletion_info) {
  if (!deletion_info.is_from_expiration())
    browsing_data::RemoveNavigationEntries(profile_, deletion_info);

  // Deleting Template URLs. This also handles expiration events.
  TemplateURLService* keywords_model =
      TemplateURLServiceFactory::GetForProfile(profile_);

  if (deletion_info.time_range().IsValid()) {
    if (keywords_model) {
      DeleteTemplateUrlsForTimeRange(keywords_model,
                                     deletion_info.time_range().begin(),
                                     deletion_info.time_range().end());
    }
  } else {
    // If the history deletion did not have a time range, delete data by
    // origin.
    auto deleted_origins =
        GetDeletedOrigins(deletion_info.deleted_urls_origin_map());

    // Move the deleted origins to avoid an expensive copy.
    if (keywords_model) {
      DeleteTemplateUrlsForDeletedOrigins(keywords_model,
                                          std::move(deleted_origins));
    }
  }

#if BUILDFLAG(IS_ANDROID)
  commerce::ShoppingService* shopping_service =
      commerce::ShoppingServiceFactory::GetForBrowserContext(profile_);
  if (shopping_service && commerce::IsMerchantViewerEnabled(
                              shopping_service->GetAccountChecker())) {
    ClearCommerceData(profile_, deletion_info);
  }
#endif
}

// static
BrowsingDataHistoryObserverService::Factory*
BrowsingDataHistoryObserverService::Factory::GetInstance() {
  static base::NoDestructor<BrowsingDataHistoryObserverService::Factory>
      instance;
  return instance.get();
}

BrowsingDataHistoryObserverService::Factory::Factory()
    : ProfileKeyedServiceFactory(
          "BrowsingDataHistoryObserverService",
          ProfileSelections::Builder()
              .WithGuest(ProfileSelection::kNone)
              // TODO(crbug.com/41488885): Check if this service is needed for
              // Ash Internals.
              .WithAshInternals(ProfileSelection::kOriginalOnly)
              .Build()) {
  DependsOn(HistoryServiceFactory::GetInstance());
  DependsOn(TabRestoreServiceFactory::GetInstance());
#if BUILDFLAG(ENABLE_SESSION_SERVICE)
  DependsOn(SessionServiceFactory::GetInstance());
#endif

#if BUILDFLAG(IS_ANDROID)
  DependsOn(MerchantViewerDataManagerFactory::GetInstance());
  DependsOn(commerce::ShoppingServiceFactory::GetInstance());
#endif
}

std::unique_ptr<KeyedService> BrowsingDataHistoryObserverService::Factory::
    BuildServiceInstanceForBrowserContext(
        content::BrowserContext* context) const {
  Profile* profile = Profile::FromBrowserContext(context);
  return std::make_unique<BrowsingDataHistoryObserverService>(profile);
}

bool BrowsingDataHistoryObserverService::Factory::
    ServiceIsCreatedWithBrowserContext() const {
  // Create this service at startup to receive all deletion events.
  return true;
}
