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

#import "ios/chrome/browser/enterprise/connectors/ios_enterprise_interstitial.h"

#import "components/application_locale_storage/application_locale_storage.h"
#import "components/enterprise/connectors/core/reporting_event_router.h"
#import "components/grit/components_resources.h"
#import "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#import "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
#import "components/safe_browsing/core/common/proto/realtimeapi.pb.h"
#import "components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h"
#import "components/security_interstitials/core/urls.h"
#import "ios/chrome/browser/enterprise/connectors/reporting/ios_reporting_event_router_factory.h"
#import "ios/chrome/browser/enterprise/connectors/reporting/reporting_util.h"
#import "ios/chrome/browser/safe_browsing/model/safe_browsing_metrics_collector_factory.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/components/security_interstitials/ios_blocking_page_metrics_helper.h"
#import "ui/base/resource/resource_bundle.h"
#import "ui/base/webui/web_ui_util.h"

namespace enterprise_connectors {

namespace {

// Creates a metrics helper for `resource`.
std::unique_ptr<security_interstitials::IOSBlockingPageMetricsHelper>
CreateMetricsHelper(const security_interstitials::UnsafeResource& resource) {
  security_interstitials::MetricsHelper::ReportDetails reporting_info;
  switch (resource.threat_type) {
    case safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_WARN:
      reporting_info.metric_prefix = "enterprise_warn";
      break;
    case safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_BLOCK:
      reporting_info.metric_prefix = "enterprise_block";
      break;
    default:
      NOTREACHED();
  }

  return std::make_unique<security_interstitials::IOSBlockingPageMetricsHelper>(
      resource.weak_web_state.get(), resource.url, reporting_info);
}

class IOSEnterpriseBlockInterstitial : public IOSEnterpriseInterstitial {
 public:
  IOSEnterpriseBlockInterstitial(
      const security_interstitials::UnsafeResource& resource,
      std::unique_ptr<EnterprisePageControllerClient> client)
      : IOSEnterpriseInterstitial(resource, std::move(client)) {
    DCHECK_EQ(resource.threat_type,
              safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_BLOCK);

    ProfileIOS* profile =
        ProfileIOS::FromBrowserState(web_state()->GetBrowserState());
    CHECK(profile);

    ReportingEventRouter* event_router =
        IOSReportingEventRouterFactory::GetForProfile(profile);

    ReportEnterpriseUrlFilteringEvent(
        UrlFilteringEventType::kBlockedSeen, request_url(),
        resource.rt_lookup_response, event_router);
  }

  // EnterpriseInterstitialBase:
  EnterpriseInterstitialBase::Type type() const override {
    return EnterpriseInterstitialBase::Type::kBlock;
  }
};

class IOSEnterpriseWarnInterstitial : public IOSEnterpriseInterstitial {
 public:
  IOSEnterpriseWarnInterstitial(
      const security_interstitials::UnsafeResource& resource,
      std::unique_ptr<EnterprisePageControllerClient> client)
      : IOSEnterpriseInterstitial(resource, std::move(client)) {
    DCHECK_EQ(resource.threat_type,
              safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_WARN);

    ProfileIOS* profile =
        ProfileIOS::FromBrowserState(web_state()->GetBrowserState());
    CHECK(profile);

    ReportingEventRouter* event_router =
        IOSReportingEventRouterFactory::GetForProfile(profile);

    ReportEnterpriseUrlFilteringEvent(
        UrlFilteringEventType::kWarnedSeen, request_url(),
        resource.rt_lookup_response, event_router);
  }

  // EnterpriseInterstitialBase:
  EnterpriseInterstitialBase::Type type() const override {
    return EnterpriseInterstitialBase::Type::kWarn;
  }
};

}  // namespace

#pragma mark - IOSEnterpriseInterstitial

// static
std::unique_ptr<IOSEnterpriseInterstitial>
IOSEnterpriseInterstitial::CreateBlockingPage(
    const security_interstitials::UnsafeResource& resource) {
  return std::make_unique<IOSEnterpriseBlockInterstitial>(
      resource, std::make_unique<EnterprisePageControllerClient>(resource));
}

// static
std::unique_ptr<IOSEnterpriseInterstitial>
IOSEnterpriseInterstitial::CreateWarningPage(
    const security_interstitials::UnsafeResource& resource) {
  return std::make_unique<IOSEnterpriseWarnInterstitial>(
      resource, std::make_unique<EnterprisePageControllerClient>(resource));
}

IOSEnterpriseInterstitial::IOSEnterpriseInterstitial(
    const security_interstitials::UnsafeResource& resource,
    std::unique_ptr<IOSEnterpriseInterstitial::EnterprisePageControllerClient>
        client)
    : IOSSecurityInterstitialPage(resource.weak_web_state.get(),
                                  resource.url,
                                  client.get()),
      unsafe_resources_({resource}),
      // Always do the `client` std::move after it's been passed to the
      // `IOSSecurityInterstitialPage` constructor or other members.
      client_(std::move(client)) {
  if (web_state()) {
    // The EnterpriseWarnPage requires the allow list to be instantiated. The
    // allow list is not intantiated when opening the interstitial directly
    // through the chrome://interstitials WebUI page.
    SafeBrowsingUrlAllowList::CreateForWebState(web_state());
  }

  client_->metrics_helper()->RecordUserDecision(
      security_interstitials::MetricsHelper::SHOW);
  client_->metrics_helper()->RecordUserInteraction(
      security_interstitials::MetricsHelper::TOTAL_VISITS);
}

IOSEnterpriseInterstitial::~IOSEnterpriseInterstitial() = default;

std::string IOSEnterpriseInterstitial::GetHtmlContents() const {
  base::DictValue load_time_data;
  PopulateInterstitialStrings(load_time_data);
  webui::SetLoadTimeDataDefaults(client_->GetApplicationLocale(),
                                 &load_time_data);
  std::string html =
      ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
          IDR_SECURITY_INTERSTITIAL_HTML);
  webui::AppendWebUiCssTextDefaults(&html);
  return webui::GetLocalizedHtml(html, load_time_data);
}

void IOSEnterpriseInterstitial::HandleCommand(
    security_interstitials::SecurityInterstitialCommand command) {
  // Delegate the primary command handling logic to the client.
  client_->HandleCommand(command);
}

bool IOSEnterpriseInterstitial::ShouldCreateNewNavigation() const {
  return true;
}

void IOSEnterpriseInterstitial::WasDismissed() {
  // Record do not proceed when tab is closed but not via page commands. For
  // example: tapping the back button or closing the tab.
  client_->metrics_helper()->RecordUserDecision(
      security_interstitials::MetricsHelper::DONT_PROCEED);
}

void IOSEnterpriseInterstitial::PopulateInterstitialStrings(
    base::DictValue& load_time_data) const {
  PopulateStrings(load_time_data);
}

const std::vector<security_interstitials::UnsafeResource>&
IOSEnterpriseInterstitial::unsafe_resources() const {
  return unsafe_resources_;
}

GURL IOSEnterpriseInterstitial::request_url() const {
  return security_interstitials::IOSSecurityInterstitialPage::request_url();
}

#pragma mark - IOSEnterpriseInterstitial::EnterprisePageControllerClient

IOSEnterpriseInterstitial::EnterprisePageControllerClient::
    EnterprisePageControllerClient(
        const security_interstitials::UnsafeResource& resource)
    : IOSBlockingPageControllerClient(
          resource.weak_web_state.get(),
          CreateMetricsHelper(resource),
          GetApplicationContext()->GetApplicationLocaleStorage()->Get()),
      request_url_(resource.url),
      threat_type_(resource.threat_type),
      threat_source_(resource.threat_source),
      rt_lookup_response_(resource.rt_lookup_response) {}

IOSEnterpriseInterstitial::EnterprisePageControllerClient::
    ~EnterprisePageControllerClient() {
  RemovePendingUnsafeNavigationDecisionsFromAllowList();
}

void IOSEnterpriseInterstitial::EnterprisePageControllerClient::HandleCommand(
    security_interstitials::SecurityInterstitialCommand command) {
  switch (command) {
    case security_interstitials::CMD_DONT_PROCEED:
      metrics_helper()->RecordUserDecision(
          security_interstitials::MetricsHelper::DONT_PROCEED);
      RemovePendingUnsafeNavigationDecisionsFromAllowList();
      GoBack();
      break;

    case security_interstitials::CMD_OPEN_HELP_CENTER:
      metrics_helper()->RecordUserInteraction(
          security_interstitials::MetricsHelper::SHOW_LEARN_MORE);
      OpenUrlInNewForegroundTab(
          GURL(security_interstitials::kEnterpriseInterstitialHelpLink));
      break;

    case security_interstitials::CMD_PROCEED: {
      // Proceed is only valid for Warning pages.
      CHECK_EQ(threat_type_,
               safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_WARN);

      if (!web_state()) {
        return;
      }

      // Report that the user bypassed the warning.
      ProfileIOS* profile =
          ProfileIOS::FromBrowserState(web_state()->GetBrowserState());
      CHECK(profile);
      ReportEnterpriseUrlFilteringEvent(
          UrlFilteringEventType::kBypassed, request_url_, rt_lookup_response_,
          IOSReportingEventRouterFactory::GetForProfile(profile));

      // Add the URL to the allowlist for this specific threat type.
      if (SafeBrowsingUrlAllowList* allow_list =
              SafeBrowsingUrlAllowList::FromWebState(web_state())) {
        allow_list->AllowUnsafeNavigations(request_url_, threat_type_);
      }

      // Record bypass metrics.
      if (safe_browsing::SafeBrowsingMetricsCollector* metrics_collector =
              SafeBrowsingMetricsCollectorFactory::GetForProfile(profile)) {
        metrics_collector->AddBypassEventToPref(threat_source_);
      }

      metrics_helper()->RecordUserDecision(
          security_interstitials::MetricsHelper::PROCEED);
      // Trigger the actual navigation/reload.
      Proceed();
      break;
    }
    default:
      NOTREACHED() << "Unsupported command received: " << command;
  }
}

void IOSEnterpriseInterstitial::EnterprisePageControllerClient::Proceed() {
  Reload();
}

void IOSEnterpriseInterstitial::EnterprisePageControllerClient::GoBack() {
  security_interstitials::IOSBlockingPageControllerClient::GoBack();
}

void IOSEnterpriseInterstitial::EnterprisePageControllerClient::
    GoBackAfterNavigationCommitted() {
  GoBack();
}

void IOSEnterpriseInterstitial::EnterprisePageControllerClient::
    RemovePendingUnsafeNavigationDecisionsFromAllowList() {
  if (web_state()) {
    SafeBrowsingUrlAllowList::FromWebState(web_state())
        ->RemovePendingUnsafeNavigationDecisions(request_url_);
  }
}

}  // namespace enterprise_connectors
