// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <memory>
#import <set>

#import "base/files/file_path.h"
#import "base/functional/callback.h"
#import "base/functional/callback_helpers.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/bind.h"
#import "base/test/task_environment.h"
#import "components/autofill/core/browser/studies/autofill_experiments.h"
#import "components/autofill/core/common/autofill_prefs.h"
#import "components/image_fetcher/ios/ios_image_decoder_impl.h"
#import "components/password_manager/core/browser/features/password_manager_features_util.h"
#import "components/password_manager/core/common/password_manager_features.h"
#import "components/password_manager/core/common/password_manager_pref_names.h"
#import "components/prefs/pref_registry_simple.h"
#import "components/prefs/testing_pref_service.h"
#import "components/signin/public/base/consent_level.h"
#import "components/signin/public/base/signin_pref_names.h"
#import "components/signin/public/base/test_signin_client.h"
#import "components/signin/public/identity_manager/device_accounts_synchronizer.h"
#import "components/signin/public/identity_manager/identity_manager_builder.h"
#import "components/signin/public/identity_manager/identity_test_environment.h"
#import "components/signin/public/identity_manager/primary_account_mutator.h"
#import "components/sync/service/sync_service_observer.h"
#import "components/sync/test/test_sync_service.h"
#import "google_apis/gaia/google_service_auth_error.h"
#import "ios/web_view/internal/signin/web_view_device_accounts_provider_impl.h"
#import "ios/web_view/internal/sync/cwv_sync_controller_internal.h"
#import "ios/web_view/public/cwv_identity.h"
#import "ios/web_view/public/cwv_sync_controller_data_source.h"
#import "ios/web_view/public/cwv_sync_controller_delegate.h"
#import "ios/web_view/public/cwv_web_view.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"

namespace ios_web_view {
namespace {

const char kTestEmail[] = "johndoe@chromium.org";

}  // namespace

class CWVSyncControllerTest : public PlatformTest {
 protected:
  CWVSyncControllerTest() {
    pref_service_.registry()->RegisterDictionaryPref(
        autofill::prefs::kAutofillSyncTransportOptIn);

    sync_service_.SetSignedOut();
  }

  base::test::TaskEnvironment task_environment_;
  signin::IdentityTestEnvironment identity_test_environment_;
  syncer::TestSyncService sync_service_;
  TestingPrefServiceSimple pref_service_;
};

TEST_F(CWVSyncControllerTest, StartSyncWithIdentity) {
  CoreAccountInfo account_info =
      identity_test_environment_.MakeAccountAvailable(kTestEmail);

  CWVIdentity* identity =
      [[CWVIdentity alloc] initWithEmail:@(kTestEmail)
                                fullName:nil
                                  gaiaID:account_info.gaia.ToNSString()];

  // Preconfigure TestSyncService as if it was enabled in transport mode.
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);

  CWVSyncController* sync_controller = [[CWVSyncController alloc]
      initWithSyncService:&sync_service_
          identityManager:identity_test_environment_.identity_manager()
              prefService:&pref_service_];
  [sync_controller startSyncWithIdentity:identity];
  EXPECT_NSEQ(sync_controller.currentIdentity.gaiaID, identity.gaiaID);

  CoreAccountInfo primary_account_info =
      identity_test_environment_.identity_manager()->GetPrimaryAccountInfo(
          signin::ConsentLevel::kSignin);
  EXPECT_EQ(primary_account_info, account_info);

  EXPECT_TRUE(
      password_manager::features_util::IsAccountStorageActive(&sync_service_));

  [sync_controller shutDown];
}

TEST_F(CWVSyncControllerTest, StartSyncWithIdentityInAuthError) {
  CWVWebView.skipAccountStorageCheckEnabled = true;
  AccountInfo account_info =
      identity_test_environment_.MakeAccountAvailable(kTestEmail);
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin, account_info);
  sync_service_.SetPersistentAuthError();
  ASSERT_FALSE(
      password_manager::features_util::IsAccountStorageActive(&sync_service_));

  // Should not crash.
  CWVSyncController* sync_controller = [[CWVSyncController alloc]
      initWithSyncService:&sync_service_
          identityManager:identity_test_environment_.identity_manager()
              prefService:&pref_service_];
  [sync_controller
      startSyncWithIdentity:[[CWVIdentity alloc]
                                initWithEmail:@(kTestEmail)
                                     fullName:base::SysUTF8ToNSString(
                                                  account_info.GetFullName()
                                                      .value_or(""))
                                       gaiaID:account_info.gaia.ToNSString()]];

  CWVWebView.skipAccountStorageCheckEnabled = false;

  [sync_controller shutDown];
}

TEST_F(CWVSyncControllerTest, StopSyncAndClearIdentity) {
  CoreAccountInfo account_info =
      identity_test_environment_.MakePrimaryAccountAvailable(
          kTestEmail, signin::ConsentLevel::kSignin);

  CWVSyncController* sync_controller = [[CWVSyncController alloc]
      initWithSyncService:&sync_service_
          identityManager:identity_test_environment_.identity_manager()
              prefService:&pref_service_];
  CWVIdentity* current_identity = sync_controller.currentIdentity;
  ASSERT_TRUE(current_identity);
  EXPECT_NSEQ(current_identity.gaiaID, account_info.gaia.ToNSString());
  EXPECT_NSEQ(current_identity.email, base::SysUTF8ToNSString(kTestEmail));

  [sync_controller stopSyncAndClearIdentity];
  EXPECT_FALSE(sync_controller.currentIdentity);

  [sync_controller shutDown];
}

TEST_F(CWVSyncControllerTest, PassphraseNeeded) {
  CWVSyncController* sync_controller = [[CWVSyncController alloc]
      initWithSyncService:&sync_service_
          identityManager:identity_test_environment_.identity_manager()
              prefService:&pref_service_];
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);
  ASSERT_FALSE(sync_service_.GetUserSettings()->IsPassphraseRequired());
  EXPECT_FALSE(sync_controller.passphraseNeeded);
  sync_service_.SetPassphraseRequired();
  EXPECT_TRUE(sync_controller.passphraseNeeded);

  [sync_controller shutDown];
}

TEST_F(CWVSyncControllerTest, TrustedVaultKeysRequired) {
  CWVSyncController* sync_controller = [[CWVSyncController alloc]
      initWithSyncService:&sync_service_
          identityManager:identity_test_environment_.identity_manager()
              prefService:&pref_service_];
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);
  ASSERT_FALSE(sync_service_.GetUserSettings()->IsTrustedVaultKeyRequired());
  EXPECT_FALSE(sync_controller.trustedVaultKeysRequired);
  sync_service_.SetTrustedVaultKeyRequired(true);
  EXPECT_TRUE(sync_controller.trustedVaultKeysRequired);

  [sync_controller shutDown];
}

TEST_F(CWVSyncControllerTest, TrustedVaultRecoverabilityDegraded) {
  CWVSyncController* sync_controller = [[CWVSyncController alloc]
      initWithSyncService:&sync_service_
          identityManager:identity_test_environment_.identity_manager()
              prefService:&pref_service_];
  sync_service_.SetTrustedVaultRecoverabilityDegraded(false);
  EXPECT_FALSE(sync_controller.trustedVaultRecoverabilityDegraded);
  sync_service_.SetTrustedVaultRecoverabilityDegraded(true);
  EXPECT_TRUE(sync_controller.trustedVaultRecoverabilityDegraded);

  [sync_controller shutDown];
}

TEST_F(CWVSyncControllerTest, DelegateDidStartAndStopSync) {
  CWVSyncController* sync_controller = [[CWVSyncController alloc]
      initWithSyncService:&sync_service_
          identityManager:identity_test_environment_.identity_manager()
              prefService:&pref_service_];

  id delegate = OCMStrictProtocolMock(@protocol(CWVSyncControllerDelegate));
  [delegate setExpectationOrderMatters:YES];
  sync_controller.delegate = delegate;

  // TestSyncService's transport state has to actually change before a callback
  // will be fired, so we have to start it before we can stop it.
  OCMExpect([delegate syncControllerDidStartSync:sync_controller]);
  OCMExpect([delegate syncControllerDidUpdateState:sync_controller]);
  OCMExpect([delegate syncControllerDidStopSync:sync_controller]);
  OCMExpect([delegate syncControllerDidUpdateState:sync_controller]);
  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);
  sync_service_.FireStateChanged();
  sync_service_.SetSignedOut();
  sync_service_.FireStateChanged();

  [delegate verify];

  [sync_controller shutDown];
}

TEST_F(CWVSyncControllerTest, DelegateDidUpdateState) {
  CWVSyncController* sync_controller = [[CWVSyncController alloc]
      initWithSyncService:&sync_service_
          identityManager:identity_test_environment_.identity_manager()
              prefService:&pref_service_];

  id delegate = OCMStrictProtocolMock(@protocol(CWVSyncControllerDelegate));
  sync_controller.delegate = delegate;

  OCMExpect([delegate syncControllerDidUpdateState:sync_controller]);
  sync_service_.FireStateChanged();
  [delegate verify];

  [sync_controller shutDown];
}

}  // namespace ios_web_view
