// Copyright 2025 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/webauthn/model/ios_chrome_passkey_client.h"

#import "base/apple/foundation_util.h"
#import "base/containers/span.h"
#import "base/functional/callback.h"
#import "base/memory/raw_ptr.h"
#import "components/metrics/metrics_pref_names.h"
#import "components/metrics/metrics_reporting_choice_service.h"
#import "components/password_manager/core/browser/password_manager_client.h"
#import "components/password_manager/core/browser/password_manager_util.h"
#import "components/password_manager/core/common/password_manager_pref_names.h"
#import "components/password_manager/ios/ios_password_manager_driver.h"
#import "components/prefs/pref_service.h"
#import "components/signin/public/base/consent_level.h"
#import "components/signin/public/identity_manager/account_info.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/sync/service/sync_service.h"
#import "components/sync/service/sync_user_settings.h"
#import "components/webauthn/ios/features.h"
#import "components/webauthn/ios/ios_passkey_client.h"
#import "components/webauthn/ios/ios_passkey_client_commands.h"
#import "components/webauthn/ios/passkey_types.h"
#import "ios/chrome/browser/credential_provider/model/credential_provider_buildflags.h"
#import "ios/chrome/browser/device_reauth/model/reauthentication_service.h"
#import "ios/chrome/browser/device_reauth/model/reauthentication_service_factory.h"
#import "ios/chrome/browser/passwords/model/password_tab_helper.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/webauthn/public/scoped_passkey_keychain_provider_override.h"
#import "ios/chrome/common/credential_provider/passkey_keychain_provider_bridge.h"
#import "ios/chrome/common/ui/reauthentication/reauthentication_protocol.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/web_state.h"
#import "ui/base/l10n/l10n_util.h"

#if BUILDFLAG(IOS_CREDENTIAL_PROVIDER_ENABLED)
#import "ios/chrome/browser/credential_provider/model/credential_provider_browser_agent.h"
#endif

// Helper class to act as a delegate for the PasskeyKeychainProviderBridge.
@interface IOSChromePasskeyClientBridgeDelegate
    : NSObject <PasskeyKeychainProviderBridgeDelegate>

- (instancetype)initWithClient:(IOSChromePasskeyClient*)client
                  reauthModule:(id<ReauthenticationProtocol>)reauthModule;

- (void)setUserVerificationStatus:
    (webauthn::PasskeyUserVerificationStatus)status;

- (webauthn::PasskeyUserVerificationStatus)userVerificationStatus;

@end

@implementation IOSChromePasskeyClientBridgeDelegate {
  raw_ptr<IOSChromePasskeyClient> _client;
  __weak id<ReauthenticationProtocol> _reauthModule;
  webauthn::PasskeyUserVerificationStatus _userVerificationStatus;
}

- (instancetype)initWithClient:(IOSChromePasskeyClient*)client
                  reauthModule:(id<ReauthenticationProtocol>)reauthModule {
  self = [super init];
  if (self) {
    _client = client;
    _reauthModule = reauthModule;
  }
  return self;
}

- (void)setUserVerificationStatus:
    (webauthn::PasskeyUserVerificationStatus)status {
  _userVerificationStatus = status;
  if (status == webauthn::PasskeyUserVerificationStatus::kRequired) {
    [_reauthModule clearAuthValidity];
  }
}

- (webauthn::PasskeyUserVerificationStatus)userVerificationStatus {
  return _userVerificationStatus;
}

- (void)performUserVerificationIfNeeded:
    (UserVerificationCompletionBlock)completion {
  if (_userVerificationStatus !=
      webauthn::PasskeyUserVerificationStatus::kRequired) {
    completion(YES);
    return;
  }

  if (![_reauthModule canAttemptReauth]) {
    completion(NO);
    return;
  }
  [_reauthModule
      attemptReauthWithLocalizedReason:
          l10n_util::GetNSString(IDS_IOS_PASSKEY_CREATION_START_REAUTH_REASON)
                  canReusePreviousAuth:YES
                               handler:^(ReauthenticationResult result) {
                                 completion(result !=
                                            ReauthenticationResult::kFailure);
                               }];
}

- (void)showWelcomeScreenWithPurpose:
            (webauthn::PasskeyWelcomeScreenPurpose)purpose
                          completion:
                              (webauthn::PasskeyWelcomeScreenAction)completion {
  [_client->GetCommandHandler() showPasskeyWelcomeScreenForPurpose:purpose
                                                        completion:completion];
}

- (void)providerDidCompleteReauthentication {
  _userVerificationStatus = webauthn::PasskeyUserVerificationStatus::kCompleted;
}

@end

IOSChromePasskeyClient::IOSChromePasskeyClient(web::WebState* web_state) {
  CHECK(web_state);
  web_state_ = web_state->GetWeakPtr();

  // IOSChromePasskeyClient should behave exactly the same way in and out of
  // incognito. In incognito mode, an interstitial is presented to the user
  // before allowing assertion or registration requests, but the logic should
  // always use the original profile.
  profile_ = ProfileIOS::FromBrowserState(web_state_->GetBrowserState())
                 ->GetOriginalProfile();
}

IOSChromePasskeyClient::~IOSChromePasskeyClient() = default;

PasskeyKeychainProviderBridge*
IOSChromePasskeyClient::GetPasskeyKeychainProviderBridge() {
  if (passkey_keychain_provider_bridge_) {
    return passkey_keychain_provider_bridge_;
  }

  PasskeyKeychainProviderBridge* test_bridge =
      ScopedPasskeyKeychainProviderBridgeOverride::Get();
  if (test_bridge) {
    passkey_keychain_provider_bridge_ = test_bridge;
  } else {
    bool metrics_reporting_enabled =
        metrics::MetricsReportingChoiceService::IsBasicMetricsReportingEnabled(
            GetApplicationContext()->GetLocalState());
    passkey_keychain_provider_bridge_ = [[PasskeyKeychainProviderBridge alloc]
          initWithEnableLogging:metrics_reporting_enabled
        navigationItemTitleView:nil];
  }

  id<ReauthenticationProtocol> reauthModule =
      ReauthenticationServiceFactory::GetForProfile(profile_)
          ->GetReauthModule();
  bridge_delegate_ = [[IOSChromePasskeyClientBridgeDelegate alloc]
      initWithClient:this
        reauthModule:reauthModule];
  passkey_keychain_provider_bridge_.delegate = bridge_delegate_;

  return passkey_keychain_provider_bridge_;
}

void IOSChromePasskeyClient::SetIOSPasskeyClientCommandsHandler(
    id<IOSPasskeyClientCommands> handler) {
  command_handler_ = handler;
}

id<IOSPasskeyClientCommands> IOSChromePasskeyClient::GetCommandHandler() const {
  return command_handler_;
}

void IOSChromePasskeyClient::FetchKeys(
    webauthn::ReauthenticatePurpose purpose,
    webauthn::PasskeyUserVerificationStatus user_verification_status,
    webauthn::FetchKeysCallback callback) {
  IOSChromePasskeyClientBridgeDelegate* delegate =
      base::apple::ObjCCast<IOSChromePasskeyClientBridgeDelegate>(
          GetPasskeyKeychainProviderBridge().delegate);

  [delegate setUserVerificationStatus:user_verification_status];

  CoreAccountInfo account =
      IdentityManagerFactory::GetForProfile(profile_)->GetPrimaryAccountInfo(
          signin::ConsentLevel::kSignin);

  auto completion_block = base::CallbackToBlock(base::BindOnce(
      [](id<IOSPasskeyClientCommands> handler,
         IOSChromePasskeyClientBridgeDelegate* delegate,
         webauthn::FetchKeysCallback inner_callback,
         webauthn::SharedKeyList trusted_vault_keys, NSError* error) {
        bool did_complete_uv =
            [delegate userVerificationStatus] ==
            webauthn::PasskeyUserVerificationStatus::kCompleted;
        std::move(inner_callback)
            .Run(std::move(trusted_vault_keys), did_complete_uv);
        [handler dismissPasskeyWelcomeScreen];
      },
      command_handler_, delegate, std::move(callback)));
  [GetPasskeyKeychainProviderBridge()
      fetchTrustedVaultKeysForGaia:account.gaia.ToNSString()
                        credential:nil
                           purpose:purpose
                        completion:completion_block];
}

void IOSChromePasskeyClient::ShowSuggestionBottomSheet(
    RequestInfo request_info) {
  [command_handler_ showPasskeySuggestionBottomSheet:request_info];
}

void IOSChromePasskeyClient::ShowCreationBottomSheet(RequestInfo request_info) {
  [command_handler_ showPasskeyCreationBottomSheet:std::move(request_info)];
}

void IOSChromePasskeyClient::ShowInterstitial(InterstitialCallback callback) {
  if (!command_handler_) {
    std::move(callback).Run(false);
    return;
  }

  [command_handler_ showPasskeyIncognitoInterstitial:std::move(callback)];
}

void IOSChromePasskeyClient::AllowPasskeyCreationInfobar(bool allowed) {
#if BUILDFLAG(IOS_CREDENTIAL_PROVIDER_ENABLED)
  if (!web_state_) {
    return;
  }

  BrowserList* browserList = BrowserListFactory::GetForProfile(profile_);
  for (Browser* browser :
       browserList->BrowsersOfType(BrowserList::BrowserType::kAll)) {
    if (auto* agent = CredentialProviderBrowserAgent::FromBrowser(browser)) {
      agent->SetInfobarAllowed(allowed);
    }
  }
#endif  // BUILDFLAG(IOS_CREDENTIAL_PROVIDER_ENABLED)
}

void IOSChromePasskeyClient::CancelPasskeyRequest(RequestInfo request_info) {
  [command_handler_ cancelPasskeyRequest:std::move(request_info)];
}

bool IOSChromePasskeyClient::IsGpmPasskeySavingEnabled() const {
  PrefService* prefs = profile_->GetPrefs();
  if (!prefs->GetBoolean(password_manager::prefs::kCredentialsEnableService) ||
      !prefs->GetBoolean(password_manager::prefs::kCredentialsEnablePasskeys)) {
    return false;
  }

  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForProfile(profile_);
  if (!sync_service || !sync_service->GetUserSettings()->GetSelectedTypes().Has(
                           syncer::UserSelectableType::kPasswords)) {
    return false;
  }
  return true;
}

bool IOSChromePasskeyClient::IsBiometricsEnabled() const {
  id<ReauthenticationProtocol> reauth_module =
      ReauthenticationServiceFactory::GetForProfile(profile_)
          ->GetReauthModule();
  return [reauth_module canAttemptReauthWithBiometrics];
}

void IOSChromePasskeyClient::OnPasskeyCreated() {
  PrefService* localState = GetApplicationContext()->GetLocalState();
  if (!password_manager_util::IsCredentialProviderEnabledOnStartup(
          localState)) {
    [command_handler_ showCredentialProviderPromoOnPasskeyCreated];
  }
}
