// Copyright 2020 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/common/credential_provider/archivable_credential.h"

#import "base/check.h"
#import "base/time/time.h"

namespace {

// Keys used to serialize properties.
NSString* const kACFaviconKey = @"favicon";
NSString* const kACGaiaKey = @"gaia";
NSString* const kACPasswordKey = @"password";
NSString* const kACRankKey = @"rank";
NSString* const kACRecordIdentifierKey = @"recordIdentifier";
NSString* const kACServiceIdentifierKey = @"serviceIdentifier";
NSString* const kACServiceNameKey = @"serviceName";
NSString* const kACRegistryControlledDomainKey = @"registryControlledDomain";
NSString* const kACUserKey = @"user";
NSString* const kACNoteKey = @"note";
NSString* const kACSyncIdKey = @"syncId";
NSString* const kACUserDisplayNameKey = @"userDisplayName";
NSString* const kACUserIdKey = @"userId";
NSString* const kACCredentialIdKey = @"credentialId";
NSString* const kACRpIdKey = @"rpId";
NSString* const kACPrivateKeyKey = @"privateKey";
NSString* const kACEncryptedKey = @"encrypted";
NSString* const kACCreationTimeKey = @"creationTime";
NSString* const kACLastUsedTimeKey = @"lastUsedTime";
NSString* const kACHiddenKey = @"hidden";
NSString* const kACHiddenTimeKey = @"hiddenTime";
NSString* const kACEditedByUserKey = @"editedByUser";

// Returns whether the strings are the same (including if both are nil) or if
// both strings have the same contents.
BOOL stringsAreEqual(NSString* lhs, NSString* rhs) {
  return rhs == lhs || [rhs isEqualToString:lhs];
}

// Returns whether the data are the same (including if both are nil) or if
// both data have the same contents.
BOOL dataAreEqual(NSData* lhs, NSData* rhs) {
  return rhs == lhs || [rhs isEqualToData:lhs];
}

}  // namespace

@implementation NSCoder (ArchivableCredential)

- (id)decodeNSStringForKey:(NSString*)key {
  return [self decodeObjectOfClass:[NSString class] forKey:key];
}

- (id)decodeNSDataForKey:(NSString*)key {
  return [self decodeObjectOfClass:[NSData class] forKey:key];
}

@end

@implementation ArchivableCredential

@synthesize favicon = _favicon;
@synthesize gaia = _gaia;
@synthesize password = _password;
@synthesize rank = _rank;
@synthesize recordIdentifier = _recordIdentifier;
@synthesize serviceIdentifier = _serviceIdentifier;
@synthesize serviceName = _serviceName;
@synthesize registryControlledDomain = _registryControlledDomain;
@synthesize username = _username;
@synthesize note = _note;
@synthesize syncId = _syncId;
@synthesize userDisplayName = _userDisplayName;
@synthesize userId = _userId;
@synthesize credentialId = _credentialId;
@synthesize rpId = _rpId;
@synthesize privateKey = _privateKey;
@synthesize encrypted = _encrypted;
@synthesize creationTime = _creationTime;
@synthesize lastUsedTime = _lastUsedTime;
@synthesize hidden = _hidden;
@synthesize hiddenTime = _hiddenTime;
@synthesize editedByUser = _editedByUser;

- (instancetype)initWithFavicon:(NSString*)favicon
                     credential:(id<Credential>)credential {
  if (credential.isPasskey) {
    // Use the passkey initilizer
    self = [self initWithFavicon:credential.favicon
                            gaia:credential.gaia
                recordIdentifier:credential.recordIdentifier
                          syncId:credential.syncId
                        username:credential.username
                 userDisplayName:credential.userDisplayName
                          userId:credential.userId
                    credentialId:credential.credentialId
                            rpId:credential.rpId
                      privateKey:credential.privateKey
                       encrypted:credential.encrypted
                    creationTime:credential.creationTime
                    lastUsedTime:credential.lastUsedTime
                          hidden:credential.hidden
                      hiddenTime:credential.hiddenTime
                    editedByUser:credential.editedByUser];
  } else {
    // Use the password initializer
    self = [self initWithFavicon:credential.favicon
                            gaia:credential.gaia
                        password:credential.password
                            rank:credential.rank
                recordIdentifier:credential.recordIdentifier
               serviceIdentifier:credential.serviceIdentifier
                     serviceName:credential.serviceName
        registryControlledDomain:credential.registryControlledDomain
                        username:credential.username
                            note:credential.note
                    lastUsedTime:credential.lastUsedTime];
  }
  return self;
}

- (instancetype)initWithFavicon:(NSString*)favicon
                           gaia:(NSString*)gaia
                       password:(NSString*)password
                           rank:(int64_t)rank
               recordIdentifier:(NSString*)recordIdentifier
              serviceIdentifier:(NSString*)serviceIdentifier
                    serviceName:(NSString*)serviceName
       registryControlledDomain:(NSString*)registryControlledDomain
                       username:(NSString*)username
                           note:(NSString*)note
                   lastUsedTime:(int64_t)lastUsedTime {
  self = [super init];
  if (self) {
    _favicon = favicon;
    _gaia = gaia;
    _password = password;
    _rank = rank;
    _recordIdentifier = recordIdentifier;
    _serviceIdentifier = serviceIdentifier;
    _serviceName = serviceName;
    _registryControlledDomain = registryControlledDomain;
    _username = username;
    _note = note;
    _lastUsedTime = lastUsedTime;
  }
  return self;
}

- (instancetype)initWithFavicon:(NSString*)favicon
                           gaia:(NSString*)gaia
               recordIdentifier:(NSString*)recordIdentifier
                         syncId:(NSData*)syncId
                       username:(NSString*)username
                userDisplayName:(NSString*)userDisplayName
                         userId:(NSData*)userId
                   credentialId:(NSData*)credentialId
                           rpId:(NSString*)rpId
                     privateKey:(NSData*)privateKey
                      encrypted:(NSData*)encrypted
                   creationTime:(int64_t)creationTime
                   lastUsedTime:(int64_t)lastUsedTime
                         hidden:(BOOL)hidden
                     hiddenTime:(int64_t)hiddenTime
                   editedByUser:(BOOL)editedByUser {
  CHECK(credentialId.length > 0);
  self = [super init];
  if (self) {
    _favicon = favicon;
    _gaia = gaia;
    _recordIdentifier = recordIdentifier;
    _syncId = syncId;
    _username = username;
    _userDisplayName = userDisplayName;
    _userId = userId;
    _credentialId = credentialId;
    _rpId = rpId;
    _privateKey = privateKey;
    _encrypted = encrypted;
    _creationTime = creationTime;
    _lastUsedTime = lastUsedTime;
    _hidden = hidden;
    _hiddenTime = hiddenTime;
    _editedByUser = editedByUser;
  }
  return self;
}

#pragma mark - Credential

- (BOOL)isPasskey {
  return self.credentialId.length > 0;
}

- (NSDate*)creationDate {
  base::TimeDelta timeInterval = base::Milliseconds(self.creationTime);
  return [NSDate dateWithTimeIntervalSince1970:timeInterval.InSeconds()];
}

// TODO(crbug.com/458784353): Convenience getter to have a valid URL for
// passkeys. Remove once all passkey related uses have been removed.
- (NSString*)serviceName {
  return self.isPasskey ? _rpId : _serviceName;
}

// TODO(crbug.com/458784353): Convenience getter to have a valid URL for
// passkeys. Remove once all passkey related uses have been removed.
- (NSString*)serviceIdentifier {
  return self.isPasskey ? _rpId : _serviceIdentifier;
}

#pragma mark - NSObject

- (BOOL)isEqual:(id)other {
  if (other == self) {
    return YES;
  } else {
    if (![other isKindOfClass:[ArchivableCredential class]]) {
      return NO;
    }
    ArchivableCredential* otherCredential = (ArchivableCredential*)other;
    return stringsAreEqual(self.favicon, otherCredential.favicon) &&
           stringsAreEqual(self.gaia, otherCredential.gaia) &&
           stringsAreEqual(self.password, otherCredential.password) &&
           self.rank == otherCredential.rank &&
           stringsAreEqual(self.recordIdentifier,
                           otherCredential.recordIdentifier) &&
           stringsAreEqual(self.serviceIdentifier,
                           otherCredential.serviceIdentifier) &&
           stringsAreEqual(self.serviceName, otherCredential.serviceName) &&
           stringsAreEqual(self.registryControlledDomain,
                           otherCredential.registryControlledDomain) &&
           stringsAreEqual(self.username, otherCredential.username) &&
           stringsAreEqual(self.note, otherCredential.note) &&
           dataAreEqual(self.syncId, otherCredential.syncId) &&
           stringsAreEqual(self.userDisplayName,
                           otherCredential.userDisplayName) &&
           dataAreEqual(self.userId, otherCredential.userId) &&
           dataAreEqual(self.credentialId, otherCredential.credentialId) &&
           stringsAreEqual(self.rpId, otherCredential.rpId) &&
           dataAreEqual(self.privateKey, otherCredential.privateKey) &&
           dataAreEqual(self.encrypted, otherCredential.encrypted) &&
           self.creationTime == otherCredential.creationTime &&
           self.lastUsedTime == otherCredential.lastUsedTime &&
           self.hidden == otherCredential.hidden &&
           self.hiddenTime == otherCredential.hiddenTime &&
           self.editedByUser == otherCredential.editedByUser;
  }
}

- (NSUInteger)hash {
  // Using record identifier xored with password or passkey id should be enough.
  return self.recordIdentifier.hash ^
         (self.isPasskey ? self.credentialId.hash : self.password.hash);
}

#pragma mark - NSSecureCoding

+ (BOOL)supportsSecureCoding {
  return YES;
}

- (void)encodeWithCoder:(NSCoder*)coder {
  [coder encodeObject:self.favicon forKey:kACFaviconKey];
  [coder encodeObject:self.gaia forKey:kACGaiaKey];
  [coder encodeObject:self.recordIdentifier forKey:kACRecordIdentifierKey];
  [coder encodeObject:self.username forKey:kACUserKey];
  if (self.isPasskey) {
    [coder encodeObject:self.syncId forKey:kACSyncIdKey];
    [coder encodeObject:self.userDisplayName forKey:kACUserDisplayNameKey];
    [coder encodeObject:self.userId forKey:kACUserIdKey];
    [coder encodeObject:self.credentialId forKey:kACCredentialIdKey];
    [coder encodeObject:self.rpId forKey:kACRpIdKey];
    [coder encodeObject:self.privateKey forKey:kACPrivateKeyKey];
    [coder encodeObject:self.encrypted forKey:kACEncryptedKey];
    [coder encodeInt64:self.creationTime forKey:kACCreationTimeKey];
    [coder encodeInt64:self.lastUsedTime forKey:kACLastUsedTimeKey];
    [coder encodeBool:self.hidden forKey:kACHiddenKey];
    [coder encodeInt64:self.hiddenTime forKey:kACHiddenTimeKey];
    [coder encodeBool:self.editedByUser forKey:kACEditedByUserKey];
  } else {
    [coder encodeObject:self.password forKey:kACPasswordKey];
    [coder encodeInt64:self.rank forKey:kACRankKey];
    [coder encodeObject:self.serviceIdentifier forKey:kACServiceIdentifierKey];
    [coder encodeObject:self.serviceName forKey:kACServiceNameKey];
    [coder encodeObject:self.registryControlledDomain
                 forKey:kACRegistryControlledDomainKey];
    [coder encodeObject:self.note forKey:kACNoteKey];
    [coder encodeInt64:self.lastUsedTime forKey:kACLastUsedTimeKey];
  }
}

- (instancetype)initWithCoder:(NSCoder*)coder {
  NSData* credentialId = [coder decodeNSDataForKey:kACCredentialIdKey];

  if (credentialId.length > 0) {
    // Use the passkey initilizer
    return [self
         initWithFavicon:[coder decodeNSStringForKey:kACFaviconKey]
                    gaia:[coder decodeNSStringForKey:kACGaiaKey]
        recordIdentifier:[coder decodeNSStringForKey:kACRecordIdentifierKey]
                  syncId:[coder decodeNSDataForKey:kACSyncIdKey]
                username:[coder decodeNSStringForKey:kACUserKey]
         userDisplayName:[coder decodeNSStringForKey:kACUserDisplayNameKey]
                  userId:[coder decodeNSDataForKey:kACUserIdKey]
            credentialId:credentialId
                    rpId:[coder decodeNSStringForKey:kACRpIdKey]
              privateKey:[coder decodeNSDataForKey:kACPrivateKeyKey]
               encrypted:[coder decodeNSDataForKey:kACEncryptedKey]
            creationTime:[coder decodeInt64ForKey:kACCreationTimeKey]
            lastUsedTime:[coder decodeInt64ForKey:kACLastUsedTimeKey]
                  hidden:[coder decodeBoolForKey:kACHiddenKey]
              hiddenTime:[coder decodeInt64ForKey:kACHiddenTimeKey]
            editedByUser:[coder decodeBoolForKey:kACEditedByUserKey]];

  } else {
    // Use the password initializer
    return [self initWithFavicon:[coder decodeNSStringForKey:kACFaviconKey]
                            gaia:[coder decodeNSStringForKey:kACGaiaKey]
                        password:[coder decodeNSStringForKey:kACPasswordKey]
                            rank:[coder decodeInt64ForKey:kACRankKey]
                recordIdentifier:
                    [coder decodeNSStringForKey:kACRecordIdentifierKey]
               serviceIdentifier:
                   [coder decodeNSStringForKey:kACServiceIdentifierKey]
                     serviceName:[coder decodeNSStringForKey:kACServiceNameKey]
        registryControlledDomain:
            [coder decodeNSStringForKey:kACRegistryControlledDomainKey]
                        username:[coder decodeNSStringForKey:kACUserKey]
                            note:[coder decodeNSStringForKey:kACNoteKey]
                    lastUsedTime:[coder decodeInt64ForKey:kACLastUsedTimeKey]];
  }
}

@end
