// 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/test/ios/wait_util.h"
#import "components/sync/protocol/webauthn_credential_specifics.pb.h"
#import "ios/chrome/common/credential_provider/archivable_credential+passkey.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

namespace {

constexpr int64_t kJan1st2024 = 1704085200;

using ArchivableCredentialTest = PlatformTest;

NSData* StringToData(std::string str) {
  return [NSData dataWithBytes:str.data() length:str.length()];
}

ArchivableCredential* TestCredential() {
  return [[ArchivableCredential alloc] initWithFavicon:@"favicon"
                                                  gaia:nil
                                              password:@"qwery123"
                                                  rank:5
                                      recordIdentifier:@"recordIdentifier"
                                     serviceIdentifier:@"serviceIdentifier"
                                           serviceName:@"serviceName"
                              registryControlledDomain:@"example.com"
                                              username:@"user"
                                                  note:@"note"
                                          lastUsedTime:0];
}

ArchivableCredential* TestPasskeyCredential() {
  return
      [[ArchivableCredential alloc] initWithFavicon:@"favicon"
                                               gaia:nil
                                   recordIdentifier:@"recordIdentifier"
                                             syncId:StringToData("syncId")
                                           username:@"username"
                                    userDisplayName:@"userDisplayName"
                                             userId:StringToData("userId")
                                       credentialId:StringToData("credentialId")
                                               rpId:@"rpId"
                                         privateKey:StringToData("privateKey")
                                          encrypted:StringToData("encrypted")
                                       creationTime:kJan1st2024
                                       lastUsedTime:kJan1st2024
                                             hidden:NO
                                         hiddenTime:kJan1st2024
                                       editedByUser:NO];
}

// Tests that an ArchivableCredential can be created.
TEST_F(ArchivableCredentialTest, create) {
  ArchivableCredential* credential =
      [[ArchivableCredential alloc] initWithFavicon:@"favicon"
                                               gaia:nil
                                           password:@"test"
                                               rank:5
                                   recordIdentifier:@"recordIdentifier"
                                  serviceIdentifier:@"serviceIdentifier"
                                        serviceName:@"serviceName"
                           registryControlledDomain:@"example.com"
                                           username:@"user"
                                               note:@"note"
                                       lastUsedTime:0];
  EXPECT_TRUE(credential);
  EXPECT_FALSE(credential.isPasskey);
}

// Tests that a passkey ArchivableCredential can be created.
TEST_F(ArchivableCredentialTest, createPasskey) {
  // In a real world scenario, "privateKey" and "encrypted" are mutually
  // exclusive. They use the same internal pointer in the passkey data structure
  // and an internal enum determines if the data pointer to represents one or
  // the other. This test verifies that either of them can be nil without issue.

  ArchivableCredential* credential =
      [[ArchivableCredential alloc] initWithFavicon:@"favicon"
                                               gaia:nil
                                   recordIdentifier:@"recordIdentifier"
                                             syncId:StringToData("syncId")
                                           username:@"username"
                                    userDisplayName:@"userDisplayName"
                                             userId:StringToData("userId")
                                       credentialId:StringToData("credentialId")
                                               rpId:@"rpId"
                                         privateKey:StringToData("test")
                                          encrypted:nil
                                       creationTime:kJan1st2024
                                       lastUsedTime:kJan1st2024
                                             hidden:NO
                                         hiddenTime:kJan1st2024
                                       editedByUser:NO];
  EXPECT_TRUE(credential);
  EXPECT_TRUE(credential.isPasskey);

  credential =
      [[ArchivableCredential alloc] initWithFavicon:@"favicon"
                                               gaia:nil
                                   recordIdentifier:@"recordIdentifier"
                                             syncId:StringToData("syncId")
                                           username:@"username"
                                    userDisplayName:@"userDisplayName"
                                             userId:StringToData("userId")
                                       credentialId:StringToData("credentialId")
                                               rpId:@"rpId"
                                         privateKey:nil
                                          encrypted:StringToData("test")
                                       creationTime:kJan1st2024
                                       lastUsedTime:kJan1st2024
                                             hidden:NO
                                         hiddenTime:kJan1st2024
                                       editedByUser:NO];
  EXPECT_TRUE(credential);
  EXPECT_TRUE(credential.isPasskey);
}

// Tests that an ArchivableCredential can be converted to NSData.
TEST_F(ArchivableCredentialTest, createData) {
  ArchivableCredential* credential = TestCredential();
  EXPECT_TRUE(credential);
  NSError* error = nil;
  NSData* data = [NSKeyedArchiver archivedDataWithRootObject:credential
                                       requiringSecureCoding:YES
                                                       error:&error];
  EXPECT_TRUE(data);
  EXPECT_FALSE(error);
}

// Tests that a passkey ArchivableCredential can be converted to NSData.
TEST_F(ArchivableCredentialTest, createPasskeyData) {
  ArchivableCredential* credential = TestPasskeyCredential();
  EXPECT_TRUE(credential);
  NSError* error = nil;
  NSData* data = [NSKeyedArchiver archivedDataWithRootObject:credential
                                       requiringSecureCoding:YES
                                                       error:&error];
  EXPECT_TRUE(data);
  EXPECT_FALSE(error);
}

// Tests that an ArchivableCredential can be retrieved from NSData.
TEST_F(ArchivableCredentialTest, retrieveData) {
  ArchivableCredential* credential = TestCredential();
  NSError* error = nil;
  NSData* data = [NSKeyedArchiver archivedDataWithRootObject:credential
                                       requiringSecureCoding:YES
                                                       error:&error];
  EXPECT_TRUE(data);
  EXPECT_FALSE(error);

  ArchivableCredential* unarchivedCredential =
      [NSKeyedUnarchiver unarchivedObjectOfClass:[ArchivableCredential class]
                                        fromData:data
                                           error:&error];
  EXPECT_TRUE(unarchivedCredential);
  EXPECT_TRUE(
      [unarchivedCredential isKindOfClass:[ArchivableCredential class]]);
  EXPECT_FALSE(unarchivedCredential.isPasskey);

  EXPECT_NSEQ(credential.favicon, unarchivedCredential.favicon);
  EXPECT_NSEQ(credential.password, unarchivedCredential.password);
  EXPECT_EQ(credential.rank, unarchivedCredential.rank);
  EXPECT_NSEQ(credential.recordIdentifier,
              unarchivedCredential.recordIdentifier);
  EXPECT_NSEQ(credential.serviceIdentifier,
              unarchivedCredential.serviceIdentifier);
  EXPECT_NSEQ(credential.serviceName, unarchivedCredential.serviceName);
  EXPECT_NSEQ(credential.registryControlledDomain,
              unarchivedCredential.registryControlledDomain);
  EXPECT_NSEQ(credential.username, unarchivedCredential.username);
}

// Tests that a passkey ArchivableCredential can be retrieved from NSData.
TEST_F(ArchivableCredentialTest, retrievePasskeyData) {
  ArchivableCredential* credential = TestPasskeyCredential();
  NSError* error = nil;
  NSData* data = [NSKeyedArchiver archivedDataWithRootObject:credential
                                       requiringSecureCoding:YES
                                                       error:&error];
  EXPECT_TRUE(data);
  EXPECT_FALSE(error);

  ArchivableCredential* unarchivedCredential =
      [NSKeyedUnarchiver unarchivedObjectOfClass:[ArchivableCredential class]
                                        fromData:data
                                           error:&error];
  EXPECT_TRUE(unarchivedCredential);
  EXPECT_TRUE(
      [unarchivedCredential isKindOfClass:[ArchivableCredential class]]);
  EXPECT_TRUE(unarchivedCredential.isPasskey);

  EXPECT_NSEQ(credential.favicon, unarchivedCredential.favicon);
  EXPECT_NSEQ(credential.recordIdentifier,
              unarchivedCredential.recordIdentifier);
  EXPECT_NSEQ(credential.syncId, unarchivedCredential.syncId);
  EXPECT_NSEQ(credential.username, unarchivedCredential.username);
  EXPECT_NSEQ(credential.userDisplayName, unarchivedCredential.userDisplayName);
  EXPECT_NSEQ(credential.userId, unarchivedCredential.userId);
  EXPECT_NSEQ(credential.credentialId, unarchivedCredential.credentialId);
  EXPECT_NSEQ(credential.rpId, unarchivedCredential.rpId);
  EXPECT_NSEQ(credential.privateKey, unarchivedCredential.privateKey);
  EXPECT_NSEQ(credential.encrypted, unarchivedCredential.encrypted);
  EXPECT_EQ(credential.creationTime, unarchivedCredential.creationTime);
  EXPECT_EQ(credential.hidden, unarchivedCredential.hidden);
  EXPECT_EQ(credential.hiddenTime, unarchivedCredential.hiddenTime);
  EXPECT_EQ(credential.editedByUser, unarchivedCredential.editedByUser);
}

// Tests ArchivableCredential equality.
TEST_F(ArchivableCredentialTest, equality) {
  ArchivableCredential* credential = TestCredential();
  ArchivableCredential* credentialIdentical = TestCredential();
  EXPECT_NSEQ(credential, credentialIdentical);
  EXPECT_EQ(credential.hash, credentialIdentical.hash);

  ArchivableCredential* credentialSameIdentifier =
      [[ArchivableCredential alloc] initWithFavicon:@"other_favicon"
                                               gaia:nil
                                           password:@"Qwerty123!"
                                               rank:credential.rank + 10
                                   recordIdentifier:@"recordIdentifier"
                                  serviceIdentifier:@"other_serviceIdentifier"
                                        serviceName:@"other_serviceName"
                           registryControlledDomain:@"otherexample.com"
                                           username:@"other_user"
                                               note:@"other_note"
                                       lastUsedTime:0];
  EXPECT_NSNE(credential, credentialSameIdentifier);

  ArchivableCredential* credentialDifferentIdentifier =
      [[ArchivableCredential alloc] initWithFavicon:@"favicon"
                                               gaia:nil
                                           password:@"123456789"
                                               rank:credential.rank
                                   recordIdentifier:@"other_recordIdentifier"
                                  serviceIdentifier:@"serviceIdentifier"
                                        serviceName:@"serviceName"
                           registryControlledDomain:@"otherexample.com"
                                           username:@"user"
                                               note:@"note"
                                       lastUsedTime:0];
  EXPECT_NSNE(credential, credentialDifferentIdentifier);

  EXPECT_NSNE(credential, nil);
}

// Tests ArchivableCredential passkey equality.
TEST_F(ArchivableCredentialTest, passkeyEquality) {
  ArchivableCredential* credential = TestPasskeyCredential();
  ArchivableCredential* credentialIdentical = TestPasskeyCredential();
  EXPECT_NSEQ(credential, credentialIdentical);
  EXPECT_EQ(credential.hash, credentialIdentical.hash);

  ArchivableCredential* credentialSameIdentifier = [[ArchivableCredential alloc]
       initWithFavicon:@"other_favicon"
                  gaia:nil
      recordIdentifier:@"recordIdentifier"
                syncId:StringToData("other_syncId")
              username:@"other_username"
       userDisplayName:@"other_userDisplayName"
                userId:StringToData("other_userId")
          credentialId:StringToData("other_credentialId")
                  rpId:@"other_rpId"
            privateKey:StringToData("other_privateKey")
             encrypted:StringToData("other_encrypted")
          creationTime:kJan1st2024 + 10
          lastUsedTime:kJan1st2024 + 10
                hidden:NO
            hiddenTime:kJan1st2024 + 10
          editedByUser:NO];
  EXPECT_NSNE(credential, credentialSameIdentifier);

  ArchivableCredential* credentialDiferentIdentifier =
      [[ArchivableCredential alloc] initWithFavicon:@"favicon"
                                               gaia:nil
                                   recordIdentifier:@"other_recordIdentifier"
                                             syncId:StringToData("syncId")
                                           username:@"username"
                                    userDisplayName:@"userDisplayName"
                                             userId:StringToData("userId")
                                       credentialId:StringToData("credentialId")
                                               rpId:@"rpId"
                                         privateKey:StringToData("privateKey")
                                          encrypted:StringToData("encrypted")
                                       creationTime:kJan1st2024
                                       lastUsedTime:kJan1st2024
                                             hidden:NO
                                         hiddenTime:kJan1st2024
                                       editedByUser:NO];
  EXPECT_NSNE(credential, credentialDiferentIdentifier);

  EXPECT_NSNE(credential, nil);
}

TEST_F(ArchivableCredentialTest, HiddenTimeNotSetForNotHiddenPasskey) {
  ArchivableCredential* credential =
      [[ArchivableCredential alloc] initWithFavicon:@"favicon"
                                               gaia:nil
                                   recordIdentifier:@"recordIdentifier"
                                             syncId:StringToData("syncId")
                                           username:@"username"
                                    userDisplayName:@"userDisplayName"
                                             userId:StringToData("userId")
                                       credentialId:StringToData("credentialId")
                                               rpId:@"rpId"
                                         privateKey:StringToData("test")
                                          encrypted:nil
                                       creationTime:kJan1st2024
                                       lastUsedTime:kJan1st2024
                                             hidden:NO
                                         hiddenTime:0
                                       editedByUser:NO];

  EXPECT_FALSE(PasskeyFromCredential(credential).has_hidden_time());
}

}  // namespace
