// 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/credential_exchange/model/credential_importer.h"

#import <vector>

#import "base/apple/foundation_util.h"
#import "base/barrier_closure.h"
#import "base/check_deref.h"
#import "base/containers/to_vector.h"
#import "base/rand_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/task/thread_pool.h"
#import "components/password_manager/core/browser/import/csv_password.h"
#import "components/password_manager/core/browser/import/import_results.h"
#import "components/password_manager/core/browser/import/password_importer.h"
#import "components/password_manager/core/browser/password_manager_util.h"
#import "components/password_manager/core/browser/ui/credential_utils.h"
#import "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
#import "components/sync/protocol/webauthn_credential_specifics.pb.h"
#import "components/webauthn/core/browser/import/import_processing_result.h"
#import "components/webauthn/core/browser/import/passkey_importer.h"
#import "components/webauthn/core/browser/passkey_model.h"
#import "components/webauthn/core/browser/passkey_model_utils.h"
#import "components/webauthn/ios/passkey_types.h"
#import "ios/chrome/browser/credential_exchange/model/credential_exchange_passkey.h"
#import "ios/chrome/browser/credential_exchange/model/credential_exchange_password.h"
#import "ios/chrome/browser/credential_exchange/model/features.h"
#import "ios/chrome/browser/credential_exchange/model/import_stats.h"
#import "ios/chrome/browser/credential_exchange/model/metrics_util.h"
#import "ios/chrome/browser/data_import/public/passkey_import_item.h"
#import "ios/chrome/browser/data_import/public/password_import_item.h"
#import "net/base/apple/url_conversions.h"
#import "url/gurl.h"

namespace {

// Count of credential types that are currently supported by the importer.
constexpr int kSupportedCredentialTypesCount = 2;

}  // namespace

@implementation CredentialImporter {
  // Imports credentials through the OS ASCredentialImportManager API.
  CredentialImportManager* _credentialImportManager;

  // Delegate for CredentialImporter.
  id<CredentialImporterDelegate> _delegate;

  // Passwords received from the exporting credential manager.
  NSArray<CredentialExchangePassword*>* _passwords;

  // Passkeys received from the exporting credential manager.
  NSArray<CredentialExchangePasskey*>* _passkeys;

  // Used to import passwords to the password store. Handles identifying errors
  // and conflicts.
  std::unique_ptr<password_manager::PasswordImporter> _passwordImporter;

  // Used to import passkeys and handle conflicts with existing passkeys.
  std::unique_ptr<webauthn::PasskeyImporter> _passkeyImporter;

  // Caches the results of initial processing of passwords.
  password_manager::ImportResults _passwordImportResult;

  // Caches the results of initial processing of passkeys.
  webauthn::ImportProcessingResult _passkeyImportResult;

  // Barrier closure that should run after initial processing finishes for all
  // supported credential types.
  base::RepeatingClosure _allCredentialTypesProcessedClosure;

  // Count of different credential types that are present on the import list.
  NSInteger _presentCredentialTypesCount;
}

- (instancetype)initWithDelegate:(id<CredentialImporterDelegate>)delegate
         savedPasswordsPresenter:
             (password_manager::SavedPasswordsPresenter*)savedPasswordsPresenter
                    passkeyModel:(webauthn::PasskeyModel*)passkeyModel {
  self = [super init];
  if (self) {
    _credentialImportManager = [[CredentialImportManager alloc] init];
    _credentialImportManager.delegate = self;
    _delegate = delegate;
    _passwordImporter = std::make_unique<password_manager::PasswordImporter>(
        CHECK_DEREF(savedPasswordsPresenter),
        /*user_confirmation_required=*/true);
    CHECK(passkeyModel);
    _passkeyImporter =
        std::make_unique<webauthn::PasskeyImporter>(*passkeyModel);
  }
  return self;
}

- (void)prepareImport:(NSUUID*)UUID {
  if (@available(iOS 26, *)) {
    [_credentialImportManager prepareImport:UUID];
  }
}

#pragma mark - Public

- (void)startImportingCredentialsWithTrustedVaultKeys:
    (webauthn::SharedKeyList)trustedVaultKeys {
  __weak __typeof(self) weakSelf = self;
  _allCredentialTypesProcessedClosure =
      base::BarrierClosure(kSupportedCredentialTypesCount, base::BindOnce(^{
                             [weakSelf onAllCredentialTypesProcessed];
                           }));

  std::vector<uint8_t> trustedVaultKey;
  if (_passkeys.count != 0) {
    CHECK_GE(trustedVaultKeys.size(), 1u);
    trustedVaultKey = trustedVaultKeys.back();
  }

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::TaskPriority::USER_VISIBLE}, base::BindOnce(^{
        return [weakSelf translateCredentialExchangePasskeys];
      }),
      base::BindOnce(^(std::vector<webauthn::PasskeyImportCandidate> passkeys) {
        [weakSelf startImportingPasskeys:std::move(passkeys)
                         trustedVaultKey:std::move(trustedVaultKey)];
      }));
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::TaskPriority::USER_VISIBLE}, base::BindOnce(^{
        return [weakSelf translateCredentialExchangePasswords];
      }),
      base::BindOnce(
          ^(std::vector<password_manager::CSVPassword> csvPasswords) {
            [weakSelf startImportingPasswords:std::move(csvPasswords)];
          }));
}

- (void)finishImportWithSelectedPasswordIds:
            (const std::vector<int>&)selectedPasswordIds
                         selectedPasskeyIds:
                             (const std::vector<int>&)selectedPasskeyIds {
  __weak __typeof(_delegate) weakDelegate = _delegate;
  __weak __typeof(self) weakSelf = self;
  base::RepeatingClosure allCredentialTypesImportedClosure =
      base::BarrierClosure(_presentCredentialTypesCount, base::BindOnce(^{
                             [weakDelegate onImportFinished];
                           }));

  if (_passwords.count > 0) {
    _passwordImporter->ContinueImport(
        selectedPasswordIds,
        base::BindOnce(^(const password_manager::ImportResults& results) {
          [weakDelegate onPasswordsImported:results];
        }).Then(allCredentialTypesImportedClosure));
  }
  if (_passkeys.count > 0) {
    _passkeyImporter->FinishImport(
        selectedPasskeyIds, base::BindOnce(^(int passkeysImported) {
                              [weakSelf onPasskeysImported:passkeysImported];
                            }).Then(allCredentialTypesImportedClosure));
  }
}

#pragma mark - CredentialImportManagerDelegate

- (void)onCredentialsTranslatedWithPasswords:
            (NSArray<CredentialExchangePassword*>*)passwords
                                    passkeys:
                                        (NSArray<CredentialExchangePasskey*>*)
                                            passkeys
                         exporterDisplayName:(NSString*)exporterDisplayName
                                       stats:(ImportStats*)stats {
  _passwords = passwords;
  _passkeys = passkeys;
  _presentCredentialTypesCount =
      (passwords.count > 0 ? 1 : 0) + (passkeys.count > 0 ? 1 : 0);
  LogImportStats(stats);
  [_delegate showImportScreenWithPasswordCount:passwords.count
                                  passkeyCount:passkeys.count
                           exporterDisplayName:exporterDisplayName];
}

#pragma mark - Private

// Converts `_passwords` into structures used by `_passwordImporter`.
- (std::vector<password_manager::CSVPassword>)
    translateCredentialExchangePasswords {
  std::vector<password_manager::CSVPassword> csvPasswords;
  csvPasswords.reserve(_passwords.count);
  for (CredentialExchangePassword* password : _passwords) {
    std::string username = base::SysNSStringToUTF8(password.username);
    std::string passwordStr = base::SysNSStringToUTF8(password.password);
    std::string note = base::SysNSStringToUTF8(password.note);

    // Even though the URL might not be valid, this status is just about parsing
    // the fields. `_passwordImporter` will handle the invalid URL internally.
    password_manager::CSVPassword::Status status =
        password_manager::CSVPassword::Status::kOK;

    // URL field is optional, so it might be nil. Pass as empty and continue.
    if (!password.URL) {
      csvPasswords.emplace_back(password_manager::CSVPassword(
          /*invalid_url=*/"", username, passwordStr, note, status));
      continue;
    }

    // Password manager expects urls to be in HTTP or HTTPS scheme. The imported
    // password might not contain it and e.g. just be an eTLD+1. Try adding the
    // scheme and validate the result.
    std::string urlStr = password.URL.absoluteString.UTF8String;
    GURL url = password_manager_util::ConstructGURLWithScheme(urlStr);
    if (password_manager::IsValidPasswordURL(url)) {
      csvPasswords.emplace_back(password_manager::CSVPassword(
          url, username, passwordStr, note, status));
    } else {
      csvPasswords.emplace_back(password_manager::CSVPassword(
          urlStr, username, passwordStr, note, status));
    }
  }
  return csvPasswords;
}

// Triggers initial processing of `passwords` handled by `_passwordImporter`.
- (void)startImportingPasswords:
    (std::vector<password_manager::CSVPassword>)passwords {
  if (passwords.empty()) {
    _allCredentialTypesProcessedClosure.Run();
    return;
  }

  __weak __typeof(self) weakSelf = self;
  _passwordImporter->Import(
      passwords, password_manager::PasswordForm::Store::kAccountStore,
      base::BindOnce(^(const password_manager::ImportResults& results) {
        [weakSelf onPasswordParsingFinished:results];
      }));
}

// Called when `_passwordImporter` finishes processing passwords. Caches the
// `results` and runs the barrier closure.
- (void)onPasswordParsingFinished:
    (const password_manager::ImportResults&)results {
  _passwordImportResult = results;
  _allCredentialTypesProcessedClosure.Run();
}

// Converts `_passkeys` into structures used by `_passkeyImporter`.
- (std::vector<webauthn::PasskeyImportCandidate>)
    translateCredentialExchangePasskeys {
  if (_passkeys.count == 0) {
    return {};
  }

  std::vector<webauthn::PasskeyImportCandidate> passkeys;

  for (CredentialExchangePasskey* passkey : _passkeys) {
    std::vector<uint8_t> hmacSecret;
    std::optional<std::string> hmacSecretAlgorithm;
    std::optional<std::vector<uint8_t>> largeBlob;
    std::optional<uint64_t> largeBlobUncompressedSize;
    if (base::FeatureList::IsEnabled(kCredentialExchangeFidoExtensions)) {
      if (passkey.hmacSecret) {
        hmacSecret =
            base::ToVector(base::apple::NSDataToSpan(passkey.hmacSecret));
      }
      if (passkey.hmacSecretAlgorithm) {
        hmacSecretAlgorithm =
            base::SysNSStringToUTF8(passkey.hmacSecretAlgorithm);
      }
      if (passkey.largeBlob) {
        largeBlob =
            base::ToVector(base::apple::NSDataToSpan(passkey.largeBlob));
      }
      if (passkey.largeBlobUncompressedSize) {
        largeBlobUncompressedSize =
            [passkey.largeBlobUncompressedSize unsignedLongLongValue];
      }
    }
    passkeys.push_back(webauthn::PasskeyImportCandidate{
        .rp_id = base::SysNSStringToUTF8(passkey.rpId),
        .user_name = base::SysNSStringToUTF8(passkey.userName),
        .user_display_name = base::SysNSStringToUTF8(passkey.userDisplayName),
        .credential_id =
            base::ToVector(base::apple::NSDataToSpan(passkey.credentialId)),
        .user_id = base::ToVector(base::apple::NSDataToSpan(passkey.userId)),
        .private_key =
            base::ToVector(base::apple::NSDataToSpan(passkey.privateKey)),
        .exporter_creation_time =
            passkey.creationDate
                ? std::optional(base::Time::FromNSDate(passkey.creationDate))
                : std::nullopt,
        .hmac_secret = std::move(hmacSecret),
        .hmac_secret_algorithm = std::move(hmacSecretAlgorithm),
        .large_blob = std::move(largeBlob),
        .large_blob_uncompressed_size = largeBlobUncompressedSize,
    });
  }

  return passkeys;
}

// Triggers initial processing of `passkeys` handled by `_passkeyImporter`.
- (void)startImportingPasskeys:
            (std::vector<webauthn::PasskeyImportCandidate>)passkeys
               trustedVaultKey:(std::vector<uint8_t>)trustedVaultKey {
  if (passkeys.empty()) {
    _allCredentialTypesProcessedClosure.Run();
    return;
  }

  __weak __typeof(self) weakSelf = self;
  _passkeyImporter->StartImport(
      std::move(passkeys), std::move(trustedVaultKey),
      base::BindOnce(^(const webauthn::ImportProcessingResult& result) {
        [weakSelf onPasskeyParsingFinished:result];
      }));
}

// Called when `_passkeyImporter` finishes processing passkeys. Caches the
// `result` and runs the barrier closure.
- (void)onPasskeyParsingFinished:
    (const webauthn::ImportProcessingResult&)result {
  _passkeyImportResult = result;
  _allCredentialTypesProcessedClosure.Run();
}

// Called when initial processing of all supported credentials types finishes.
// If there are no conflicts to be resolved by the user across all credential
// types, triggers actual import of the data. Otherwise, notifies the delegate
// to display conflict resolution UI first.
- (void)onAllCredentialTypesProcessed {
  if (_passkeyImportResult.conflicts.empty() &&
      _passwordImportResult.displayed_entries.empty()) {
    [self finishImportWithSelectedPasswordIds:{} selectedPasskeyIds:{}];
    return;
  }

  NSArray<PasswordImportItem*>* passwords = [PasswordImportItem
      passwordImportItemsFromImportResults:_passwordImportResult];
  NSArray<PasskeyImportItem*>* passkeys = [PasskeyImportItem
      passkeyImportItemsFromImportedPasskeyInfos:_passkeyImportResult
                                                     .conflicts];
  [_delegate showConflictResolutionScreenWithPasswords:passwords
                                              passkeys:passkeys];
}

- (void)onPasskeysImported:(int)passkeysImported {
  [_delegate onPasskeysImported:passkeysImported
                        invalid:[PasskeyImportItem
                                    passkeyImportItemsFromImportedPasskeyInfos:
                                        _passkeyImportResult.errors]];
}

- (void)onImportError {
  [_delegate onImportError];
}

@end
