// Copyright 2023 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/supervised_user/model/supervised_user_error_container.h"

#import <string>

#import "base/feature_list.h"
#import "base/functional/callback_helpers.h"
#import "base/memory/ptr_util.h"
#import "base/notreached.h"
#import "components/supervised_user/core/browser/family_link_settings_service.h"
#import "components/supervised_user/core/browser/supervised_user_service.h"
#import "components/supervised_user/core/common/features.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/parent_access_commands.h"
#import "ios/chrome/browser/supervised_user/model/family_link_settings_service_factory.h"
#import "ios/chrome/browser/supervised_user/model/ios_web_content_handler_impl.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_service_factory.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_url_filtering_service_factory.h"
#import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/web_state.h"

namespace {

const char* BoolToString(bool value) {
  return value ? "true" : "false";
}

// Updates the request status on the Interstitial upon making
// a permission request.
// The method is invoked as a callback, so it is recommended to
// bind a weak pointer to the webstate, in case it has been invalidated.
void OnRequestUrlAccess(base::WeakPtr<web::WebState> weak_web_state,
                        bool is_main_frame,
                        bool is_request_successful) {
  web::WebState* web_state = weak_web_state.get();
  if (!web_state) {
    return;
  }
  NSString* js_to_execute =
      [NSString stringWithFormat:@"setRequestStatus(%s, %s)",
                                 BoolToString(is_request_successful),
                                 BoolToString(is_main_frame)];
  // Trigger Intersitial JS method.
  web_state->ExecuteUserJavaScript(js_to_execute);
}

}  // namespace

const char kSupervisedUserInterstitialType[] = "kSupervisedUserInterstitial";

SupervisedUserErrorContainer::SupervisedUserErrorContainer(
    web::WebState* web_state)
    : family_link_settings_service_(
          *supervised_user::FamilyLinkSettingsServiceFactory::GetForProfile(
              ProfileIOS::FromBrowserState(web_state->GetBrowserState()))),
      supervised_user_service_(*supervised_user::SupervisedUserServiceFactory::GetForProfile(
          ProfileIOS::FromBrowserState(web_state->GetBrowserState()))),
      supervised_user_url_filtering_service_(
          *supervised_user::SupervisedUserUrlFilteringServiceFactory::
              GetForProfile(
                  ProfileIOS::FromBrowserState(web_state->GetBrowserState()))),
      web_state_(web_state) {
  url_filtering_service_observation_.Observe(
      &supervised_user_url_filtering_service_.get());
}

SupervisedUserErrorContainer::~SupervisedUserErrorContainer() = default;

SupervisedUserErrorContainer::SupervisedUserErrorInfo::SupervisedUserErrorInfo(
    supervised_user::WebFilteringResult filtering_result,
    bool is_main_frame)
    : filtering_result_(filtering_result), is_main_frame_(is_main_frame) {}

void SupervisedUserErrorContainer::SetSupervisedUserErrorInfo(
    std::unique_ptr<SupervisedUserErrorInfo> error_info) {
  supervised_user_error_info_ = std::move(error_info);
}

std::unique_ptr<supervised_user::SupervisedUserInterstitial>
SupervisedUserErrorContainer::CreateSupervisedUserInterstitial(
    SupervisedUserErrorInfo& error_info) {
  std::unique_ptr<IOSWebContentHandlerImpl> web_content_handler =
      std::make_unique<IOSWebContentHandlerImpl>(web_state_, commands_handler_,
                                                 error_info.is_main_frame());

  std::unique_ptr<supervised_user::SupervisedUserInterstitial> interstitial =
      supervised_user::SupervisedUserInterstitial::Create(
          std::move(web_content_handler), supervised_user_service_.get(),
          family_link_settings_service_.get(), error_info.filtering_result(),
          // User name needed only for the local web approval flow, not
          // applicable for iOS.
          /*supervised_user_name=*/std::u16string());
  return interstitial;
}

void SupervisedUserErrorContainer::HandleCommand(
    supervised_user::SupervisedUserInterstitial& interstitial,
    security_interstitials::SecurityInterstitialCommand command) {
  if (command == security_interstitials::SecurityInterstitialCommand::
                     CMD_REQUEST_SITE_ACCESS_PERMISSION) {
    RequestUrlAccessRemoteCallback callback =
        base::BindOnce(&OnRequestUrlAccess, web_state_->GetWeakPtr(),
                       interstitial.web_content_handler()->IsMainFrame());
    interstitial.RequestUrlAccessRemote(
        base::BindOnce(&SupervisedUserErrorContainer::OnRequestCreated,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                       interstitial.filtering_result().url));
  } else if (command ==
             security_interstitials::SecurityInterstitialCommand::CMD_PROCEED) {
    interstitial.RequestUrlAccessLocal(base::DoNothing());
  } else if (command == security_interstitials::SecurityInterstitialCommand::
                            CMD_DONT_PROCEED) {
    interstitial.GoBack();
  }
}

bool SupervisedUserErrorContainer::IsRemoteApprovalPendingForUrl(
    const GURL& url) {
  return requested_hosts_.contains(url.GetHost());
}

void SupervisedUserErrorContainer::URLFilterCheckCallback(
    supervised_user::WebFilteringResult result) {
  auto* blocking_tab_helper =
      security_interstitials::IOSBlockingPageTabHelper::FromWebState(
          web_state_);
  CHECK(blocking_tab_helper);
  security_interstitials::IOSSecurityInterstitialPage* blocking_page =
      blocking_tab_helper->GetCurrentBlockingPage();

  // Early exit if the blocking page is not a supervised user interstitial.
  if (blocking_page &&
      blocking_page->GetInterstitialType() != kSupervisedUserInterstitialType) {
    return;
  }

  bool is_showing_supervised_user_interstitial_for_url = false;
  bool is_main_frame = true;

  if (blocking_page) {
    // If a blocking_page exists here, then it has the right type.
    SupervisedUserInterstitialBlockingPage* supervised_user_blocking_page =
        static_cast<SupervisedUserInterstitialBlockingPage*>(blocking_page);
    is_showing_supervised_user_interstitial_for_url =
        supervised_user_blocking_page->interstitial().filtering_result().url ==
        result.url;
    is_main_frame = supervised_user_blocking_page->interstitial()
                        .web_content_handler()
                        ->IsMainFrame();
  }

  bool should_show_interstitial = result.IsBlocked();

  if (is_showing_supervised_user_interstitial_for_url !=
      should_show_interstitial) {
    // The present interstitial framework on iOS supports main frames only.
    // It it is not possible to obtain or refresh a subframe interstitial.
    if (is_main_frame && web_state_->IsRealized()) {
      web_state_->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
                                                 /*check_for_repost=*/true);
    }
  }
}

void SupervisedUserErrorContainer::OnUrlFilteringServiceChanged() {
  supervised_user_url_filtering_service_->GetFilteringBehavior(
      web_state_->GetLastCommittedURL(),
      /*skip_manual_parent_filter=*/false,
      base::BindOnce(&SupervisedUserErrorContainer::URLFilterCheckCallback,
                     weak_ptr_factory_.GetWeakPtr()),
      supervised_user::WebFilterMetricsOptions());
  MaybeUpdatePendingApprovals();
}

void SupervisedUserErrorContainer::OnRequestCreated(
    RequestUrlAccessRemoteCallback callback,
    const GURL& url,
    bool successfully_created_request) {
  if (successfully_created_request) {
    requested_hosts_.insert(url.GetHost());
  }
  std::move(callback).Run(successfully_created_request);
}

void SupervisedUserErrorContainer::MaybeUpdatePendingApprovals() {
  for (auto iter = requested_hosts_.begin(); iter != requested_hosts_.end();) {
    supervised_user::WebFilteringResult result =
        supervised_user_url_filtering_service_->GetFilteringBehavior(
            GURL(*iter));

    if (result.IsFromManualList() && result.IsAllowed()) {
      iter = requested_hosts_.erase(iter);
    } else {
      iter++;
    }
  }
}

void SupervisedUserErrorContainer::SetParentAccessBottomSheetHandler(
    id<ParentAccessCommands> commands_handler) {
  if (!commands_handler) {
    // Means that the web state has been destroyed therefore dismiss the
    // bottom sheet if it's shown.
    [commands_handler_ hideParentAccessBottomSheet];
  }
  commands_handler_ = commands_handler;
}

SupervisedUserInterstitialBlockingPage::SupervisedUserInterstitialBlockingPage(
    std::unique_ptr<supervised_user::SupervisedUserInterstitial> interstitial,
    std::unique_ptr<security_interstitials::IOSBlockingPageControllerClient>
        controller_client,
    SupervisedUserErrorContainer* error_container,
    web::WebState* web_state)
    : security_interstitials::IOSSecurityInterstitialPage(
          web_state,
          interstitial->filtering_result().url,
          controller_client.get()),
      interstitial_(std::move(interstitial)),
      controller_client_(std::move(controller_client)),
      web_state_(web_state),
      error_container_(error_container) {
  CHECK(interstitial_);
  scoped_observation_.Observe(web_state);
}

SupervisedUserInterstitialBlockingPage::
    ~SupervisedUserInterstitialBlockingPage() = default;

void SupervisedUserInterstitialBlockingPage::HandleCommand(
    security_interstitials::SecurityInterstitialCommand command) {
  CHECK(error_container_);
  error_container_->HandleCommand(*interstitial_, command);
}

bool SupervisedUserInterstitialBlockingPage::ShouldCreateNewNavigation() const {
  NOTREACHED();
}

void SupervisedUserInterstitialBlockingPage::PopulateInterstitialStrings(
    base::DictValue& load_time_data) const {
  NOTREACHED();
}

std::string_view SupervisedUserInterstitialBlockingPage::GetInterstitialType()
    const {
  return kSupervisedUserInterstitialType;
}

// Note: The SupervisedUserInterstitialBlockingPage has a pointer to
// error_container_ (which is WebStateUserData helper) and is managed by
// SupervisedUserInterstitialBlockingPage (another WebStateUserData helper).
// The order of their destruction is unspecified, so the present object
// observes the `web_state` to reset the pointer.
void SupervisedUserInterstitialBlockingPage::WebStateDestroyed(
    web::WebState* web_state) {
  DCHECK(scoped_observation_.IsObservingSource(web_state));
  error_container_ = nullptr;
  scoped_observation_.Reset();
}
