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

#include "chrome/browser/btm/btm_browser_signin_detector.h"

#include <cstddef>
#include <cstring>
#include <memory>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_file_util.h"
#include "base/test/test_future.h"
#include "chrome/browser/btm/btm_browser_signin_detector_factory.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/chrome_signin_client_test_util.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/test/base/testing_profile.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/account_managed_status_finder.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "content/public/browser/btm_service.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/scheme_host_port.h"

namespace {
constexpr char kIdentityProviderDomain[] = "google.com";

// Simple wrapper to serves as a POD for the test accounts.
struct TestAccount {
  const std::string email;
  const std::string host_domain;
};

std::unique_ptr<TestingProfile> BuildTestingProfile(
    network::TestURLLoaderFactory& test_url_loader_factory) {
  TestingProfile::Builder builder;
  builder.SetSharedURLLoaderFactory(
      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
          &test_url_loader_factory));

  builder.AddTestingFactories(
      IdentityTestEnvironmentProfileAdaptor::
          GetIdentityTestEnvironmentFactoriesWithAppendedFactories(
              {TestingProfile::TestingFactory{
                  ChromeSigninClientFactory::GetInstance(),
                  base::BindRepeating(&BuildChromeSigninClientWithURLLoader,
                                      &test_url_loader_factory)}}));

  return IdentityTestEnvironmentProfileAdaptor::
      CreateProfileForIdentityTestEnvironment(builder);
}
}  // namespace

class BrowserSigninDetectorServiceTest : public testing::Test {
 protected:
  void SetUp() override {
    testing::Test::SetUp();
    profile_ = BuildTestingProfile(test_url_loader_factory_);

    identity_test_environment_profile_adaptor_ =
        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_.get());

    identity_test_env()->SetTestURLLoaderFactory(&test_url_loader_factory_);
  }

  // This initialization of the BTM service will instantiate a copy of the
  // detector under test.
  void InitBtmService() {
    BtmBrowserSigninDetectorFactory::GetInstance()
        ->EnableWaitForServiceForTesting();
    btm_service_ = content::BtmService::Get(profile_.get());
    EXPECT_NE(btm_service_, nullptr);
    BtmBrowserSigninDetectorFactory::GetInstance()->WaitForServiceForTesting(
        profile_.get());
  }

  TestingProfile* profile() { return profile_.get(); }

  signin::IdentityManager* identity_manager() {
    return identity_test_env()->identity_manager();
  }

  signin::IdentityTestEnvironment* identity_test_env() {
    return identity_test_environment_profile_adaptor_->identity_test_env();
  }

  content::BtmService* btm_service() { return btm_service_; }

  GURL GetURL(std::string_view domain) {
    return GURL(base::StrCat({"http://", domain}));
  }

  void SimulateSuccessfulFetchOfAccountInfo(const TestAccount* test_account,
                                            const AccountInfo* account_info) {
    identity_test_env()->SimulateSuccessfulFetchOfAccountInfo(
        account_info->account_id, account_info->email, account_info->gaia,
        test_account->host_domain,
        base::StrCat({"full_name-", test_account->email}),
        base::StrCat({"given_name-", test_account->email}),
        base::StrCat({"local-", test_account->email}),
        base::StrCat({"full_name-", test_account->email}));
  }

  bool DidSiteHaveUserActivation(std::string_view domain) {
    base::test::TestFuture<bool> future;
    btm_service()->DidSiteHaveUserActivationSince(
        GetURL(domain), base::Time::Min(), future.GetCallback());
    return future.Get();
  }

  content::BrowserTaskEnvironment task_environment_;

  const TestAccount kNonEnterpriseAccount = {"foo@bar.com", ""};
  const TestAccount kEnterpriseAccount = {"foo@enterprise.com",
                                          "enterprise.com"};
  const TestAccount kEnterpriseIdentityProviderDomainAccount = {
      base::StrCat({"foo@", kIdentityProviderDomain}), kIdentityProviderDomain};

 private:
  network::TestURLLoaderFactory test_url_loader_factory_;
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
      identity_test_environment_profile_adaptor_;
  raw_ptr<content::BtmService> btm_service_;
};

TEST_F(BrowserSigninDetectorServiceTest, AccountWithNoExtendedAccountInfo) {
  InitBtmService();

  AccountInfo account_info = identity_test_env()->MakePrimaryAccountAvailable(
      kNonEnterpriseAccount.email, signin::ConsentLevel::kSignin);

  // Makes sure there are no domain available for the created account.
  base::MockCallback<base::OnceClosure> outcome_determined;
  signin::AccountManagedStatusFinder finder(identity_manager(), account_info,
                                            outcome_determined.Get());
  EXPECT_EQ(finder.GetOutcome(),
            signin::AccountManagedStatusFinder::Outcome::kPending);

  // There should be no recorded interactions.
  ASSERT_FALSE(DidSiteHaveUserActivation(kIdentityProviderDomain));
}

TEST_F(BrowserSigninDetectorServiceTest, NonEnterpriseAccount) {
  InitBtmService();

  AccountInfo account_info = identity_test_env()->MakePrimaryAccountAvailable(
      kNonEnterpriseAccount.email, signin::ConsentLevel::kSignin);

  SimulateSuccessfulFetchOfAccountInfo(&kNonEnterpriseAccount, &account_info);

  // Makes sure we have a non-enterprise account created based on the test
  // account provided.
  signin::AccountManagedStatusFinder finder(identity_manager(), account_info,
                                            base::DoNothing());
  EXPECT_EQ(finder.GetOutcome(),
            signin::AccountManagedStatusFinder::Outcome::kConsumerNotWellKnown);

  // There should be a recorded interaction for the`kIdentityProviderDomain`.
  EXPECT_TRUE(DidSiteHaveUserActivation(kIdentityProviderDomain));
}

TEST_F(BrowserSigninDetectorServiceTest, EnterpriseAccount) {
  InitBtmService();

  AccountInfo account_info = identity_test_env()->MakePrimaryAccountAvailable(
      kEnterpriseAccount.email, signin::ConsentLevel::kSignin);

  SimulateSuccessfulFetchOfAccountInfo(&kEnterpriseAccount, &account_info);

  // Makes sure we have an enterprise account created based on the test account
  // provided.
  signin::AccountManagedStatusFinder finder(identity_manager(), account_info,
                                            base::DoNothing());
  EXPECT_EQ(finder.GetOutcome(),
            signin::AccountManagedStatusFinder::Outcome::kEnterprise);

  // There should be a recorded interaction for the`kIdentityProviderDomain`.
  EXPECT_TRUE(DidSiteHaveUserActivation(kIdentityProviderDomain));

  // There should be a recorded interaction for the
  // `kEnterpriseAccount.host_domain`.
  EXPECT_TRUE(DidSiteHaveUserActivation(kEnterpriseAccount.host_domain));
}

TEST_F(BrowserSigninDetectorServiceTest,
       EnterpriseIdentityProviderDomainAccount) {
  InitBtmService();

  AccountInfo account_info = identity_test_env()->MakePrimaryAccountAvailable(
      kEnterpriseIdentityProviderDomainAccount.email,
      signin::ConsentLevel::kSignin);

  SimulateSuccessfulFetchOfAccountInfo(
      &kEnterpriseIdentityProviderDomainAccount, &account_info);

  // Makes sure we have an enterprise account created based on the test account
  // provided.
  signin::AccountManagedStatusFinder finder(identity_manager(), account_info,
                                            base::DoNothing());
  EXPECT_EQ(
      finder.GetOutcome(),
      signin::AccountManagedStatusFinder::Outcome::kEnterpriseGoogleDotCom);

  // There should be a recorded interaction for the `kIdentityProviderDomain`.
  EXPECT_TRUE(DidSiteHaveUserActivation(kIdentityProviderDomain));
}

TEST_F(BrowserSigninDetectorServiceTest, LateObservation) {
  AccountInfo account_info_1 = identity_test_env()->MakePrimaryAccountAvailable(
      kNonEnterpriseAccount.email, signin::ConsentLevel::kSignin);
  AccountInfo account_info_2 =
      identity_test_env()->MakeAccountAvailable(kEnterpriseAccount.email);

  identity_test_env()->SetCookieAccounts(
      {{account_info_1.email, account_info_1.gaia},
       {account_info_2.email, account_info_2.gaia}});

  SimulateSuccessfulFetchOfAccountInfo(&kNonEnterpriseAccount, &account_info_1);
  SimulateSuccessfulFetchOfAccountInfo(&kEnterpriseAccount, &account_info_2);

  // The initialization will instantiate a detector at this moment to simulate a
  // late detection.
  InitBtmService();

  // There should be a recorded interaction for the `kIdentityProviderDomain`.
  EXPECT_TRUE(DidSiteHaveUserActivation(kIdentityProviderDomain));

  // There should be a recorded interaction for the
  // `kEnterpriseAccount.host_domain`.
  EXPECT_TRUE(DidSiteHaveUserActivation(kEnterpriseAccount.host_domain));
}
