// Copyright 2021 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_provider/model/credential_provider_migrator.h"

#import <UIKit/UIKit.h>

#import "base/metrics/histogram_functions.h"
#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "components/password_manager/core/browser/password_form.h"
#import "components/password_manager/core/browser/password_store/password_form_converters.h"
#import "components/password_manager/core/browser/password_store/password_store_interface.h"
#import "components/sync/protocol/webauthn_credential_specifics.pb.h"
#import "components/webauthn/core/browser/passkey_model.h"
#import "components/webauthn/core/browser/passkey_model_utils.h"
#import "ios/chrome/browser/credential_provider/model/archivable_credential+password_form.h"
#import "ios/chrome/common/credential_provider/archivable_credential+passkey.h"
#import "ios/chrome/common/credential_provider/passkey_model_observer_bridge.h"
#import "ios/chrome/common/credential_provider/user_defaults_credential_store.h"

using password_manager::PasswordStoreInterface;

NSErrorDomain const kCredentialProviderMigratorErrorDomain =
    @"kCredentialProviderMigratorErrorDomain";

// Name of the passkey migration related histogram.
static constexpr char kPasskeysIOSMigration[] = "Passkeys.IOSMigration";

@interface CredentialProviderMigrator () <PasskeyModelObserverDelegate> {
  // Passkey store.
  raw_ptr<webauthn::PasskeyModel> _passkeyStore;

  // Observer to know when the passkey store is destroyed.
  std::unique_ptr<PasskeyModelObserverBridge> _passkeyModelObserverBridge;
}

// Key used to retrieve the temporal storage.
@property(nonatomic, copy) NSString* key;

// User defaults containing the temporal storage.
@property(nonatomic, copy) NSUserDefaults* userDefaults;

// Temporal store containing the passwords created in CPE extension.
@property(nonatomic, strong) UserDefaultsCredentialStore* temporalStore;

// Password manager store, where passwords will be migrated to.
@property(nonatomic, assign) scoped_refptr<PasswordStoreInterface>
    passwordStore;

// The GAIA ID of the profile undergoing migration.
@property(nonatomic, copy) NSString* gaiaID;

@end

@implementation CredentialProviderMigrator

- (instancetype)initWithUserDefaults:(NSUserDefaults*)userDefaults
                                 key:(NSString*)key
                                gaia:(NSString*)gaiaID
                       passwordStore:
                           (scoped_refptr<PasswordStoreInterface>)passwordStore
                        passkeyStore:(webauthn::PasskeyModel*)passkeyStore {
  self = [super init];
  if (self) {
    _key = key;
    _userDefaults = userDefaults;
    _passwordStore = passwordStore;
    _passkeyStore = passkeyStore;
    _gaiaID = gaiaID;
    if (_passkeyStore) {
      _passkeyModelObserverBridge =
          std::make_unique<PasskeyModelObserverBridge>(self, _passkeyStore);
    }
  }
  return self;
}

- (void)startMigrationWithCompletion:(void (^)(BOOL success,
                                               NSError* error))completion {
  if (self.temporalStore) {
    NSError* error =
        [NSError errorWithDomain:kCredentialProviderMigratorErrorDomain
                            code:kCredentialProviderMigratorErrorAlreadyRunning
                        userInfo:nil];
    completion(NO, error);
    return;
  }

  self.temporalStore = [[UserDefaultsCredentialStore alloc]
      initWithUserDefaults:self.userDefaults
                       key:self.key];
  NSArray<id<Credential>>* credentials = self.temporalStore.credentials.copy;

  bool importPasskeys = _passkeyStore && _passkeyStore->IsReady();
  base::flat_set<std::string> syncIds;
  if (importPasskeys) {
    syncIds = _passkeyStore->GetAllSyncIds();
  }

  for (id<Credential> credential in credentials) {
    if (credential.isPasskey) {
      // For passkeys, gaiaID cannot be nil as passkeys require a user to be
      // signed in. Also, the account's gaiaID must match the credential's
      // gaiaID, otherwise the passkey belongs to a different account.
      if (credential.gaia == nil || self.gaiaID == nil ||
          ![credential.gaia isEqualToString:self.gaiaID]) {
        continue;
      }

      // If this happens too early (before the passkey store is ready), the
      // migration will be re-triggered later for that passkey store by
      // CredentialProviderMigratorAppAgent.
      if (!importPasskeys) {
        continue;
      }

      std::string syncId(static_cast<const char*>(credential.syncId.bytes),
                         credential.syncId.length);
      if (syncIds.contains(syncId)) {
        // If the passkey already exists, only update its last used time, and
        // only do so if it's newer and the credential is still active.
        std::string rpId = base::SysNSStringToUTF8(credential.rpId);
        std::string credentialId(
            static_cast<const char*>(credential.credentialId.bytes),
            credential.credentialId.length);
        std::optional<sync_pb::WebauthnCredentialSpecifics>
            credential_specifics = _passkeyStore->GetPasskey(
                rpId, credentialId,
                webauthn::PasskeyModel::ShadowedCredentials::kExclude);
        if (!credential_specifics.has_value()) {
          continue;
        }

        if (credential_specifics->hidden() != credential.hidden) {
          // TODO(crbug.com/432260316): Log metrics.
          // TODO(crbug.com/432260316): Add PasskeyChangeQuotaTracker.
          if (credential.hidden) {
            _passkeyStore->HidePasskey(
                credentialId, base::Time::FromMillisecondsSinceUnixEpoch(
                                  credential.hiddenTime));
          } else {
            _passkeyStore->UnhidePasskey(credentialId);
          }
        }

        std::string username = base::SysNSStringToUTF8(credential.username);
        if (credential_specifics->user_name() != username) {
          _passkeyStore->UpdatePasskey(
              credentialId,
              {.user_name = username,
               .user_display_name = credential_specifics->user_display_name()},
              /*updated_by_user=*/false);
        }

        if (credential_specifics->last_used_time_windows_epoch_micros() <
            credential.lastUsedTime) {
          _passkeyStore->UpdatePasskeyTimestamp(
              credentialId, base::Time::FromDeltaSinceWindowsEpoch(
                                base::Microseconds(credential.lastUsedTime)));
          base::UmaHistogramEnumeration(
              kPasskeysIOSMigration, PasskeysMigrationStatus::kPasskeyUpdated);
        }
      } else {
        sync_pb::WebauthnCredentialSpecifics passkey =
            PasskeyFromCredential(credential);
        if (webauthn::passkey_model_utils::IsGpmPasskeyValid(passkey)) {
          _passkeyStore->CreatePasskey(passkey);
          base::UmaHistogramEnumeration(
              kPasskeysIOSMigration, PasskeysMigrationStatus::kPasskeyCreated);
        } else {
          base::UmaHistogramEnumeration(
              kPasskeysIOSMigration, PasskeysMigrationStatus::kInvalidPasskey);
        }
      }
    } else {
      // For passwords, either the password is a local password not associated
      // with a user account, which means that the gaiaID is nil and the
      // password is being imported locally OR the password is associated with a
      // user account, which means that the password's gaiaID must match the
      // current account's gaiaID.
      bool validLocalPassword = credential.gaia == nil && self.gaiaID == nil;
      bool validAccountPassword = [credential.gaia isEqualToString:self.gaiaID];
      if (!validLocalPassword && !validAccountPassword) {
        continue;
      }

      password_manager::PasswordForm form =
          PasswordFormFromCredential(credential);
      self.passwordStore->AddLogin(
          password_manager::FromPasswordForm(std::move(form)));
    }
    [self.temporalStore
        removeCredentialWithRecordIdentifier:credential.recordIdentifier];
  }
  __weak __typeof__(self) weakSelf = self;
  [self.temporalStore saveDataWithCompletion:^(NSError* error) {
    DCHECK(!error);
    weakSelf.temporalStore = nil;
    completion(error == nil, error);
  }];
}

#pragma mark - PasskeyModelObserverDelegate

- (void)passKeyModelShuttingDown:(webauthn::PasskeyModel*)passkeyModel {
  CHECK_EQ(_passkeyStore, passkeyModel);
  _passkeyModelObserverBridge.reset();
  _passkeyStore = nullptr;
}

- (void)passkeyModelIsReady:(webauthn::PasskeyModel*)passkeyModel {
}

- (void)passkeyModelDidChange {
}

@end
