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

#include <vector>

#include "base/base64.h"
#include "base/test/run_until.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/platform_browser_test.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/content/browser/test_autofill_client_injector.h"
#include "components/autofill/content/browser/test_autofill_manager_injector.h"
#include "components/autofill/content/browser/test_content_autofill_client.h"
#include "components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager.h"
#include "components/autofill/core/browser/foundations/autofill_manager_test_api.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager_test_api.h"
#include "components/autofill/core/browser/foundations/test_autofill_manager_waiter.h"
#include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
#include "components/autofill/core/browser/ui/test_autofill_external_delegate.h"
#include "components/autofill/core/common/signatures.h"
#include "components/one_time_tokens/core/browser/gmail_otp_backend.h"
#include "components/one_time_tokens/core/browser/one_time_token.h"
#include "components/one_time_tokens/core/browser/one_time_token_backend_notification.h"
#include "components/one_time_tokens/core/browser/one_time_token_retrieval_error.h"
#include "components/one_time_tokens/core/browser/one_time_token_service_impl.h"
#include "components/one_time_tokens/core/browser/sms_otp_backend.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace autofill {

namespace {

// This implementation is for testing. It lets us manually control and simulate
// the moment an SMS is received for one-time passwords (OTP).
class FakeSmsOtpBackend : public one_time_tokens::SmsOtpBackend {
 public:
  using CallbackType = base::OnceCallback<void(
      base::expected<one_time_tokens::OneTimeToken,
                     one_time_tokens::OneTimeTokenRetrievalError>)>;

  FakeSmsOtpBackend() = default;
  ~FakeSmsOtpBackend() override = default;

  // one_time_tokens::SmsOtpBackend:
  void RetrieveSmsOtp(CallbackType callback) override;

  // Simulates the reception of an SMS.
  void NotifyCallbacks(
      base::expected<one_time_tokens::OneTimeToken,
                     one_time_tokens::OneTimeTokenRetrievalError> reply);

  size_t num_callbacks() const { return callbacks_.size(); }

 private:
  std::vector<CallbackType> callbacks_;
};

void FakeSmsOtpBackend::RetrieveSmsOtp(
    FakeSmsOtpBackend::CallbackType callback) {
  callbacks_.push_back(std::move(callback));
}

void FakeSmsOtpBackend::NotifyCallbacks(
    base::expected<one_time_tokens::OneTimeToken,
                   one_time_tokens::OneTimeTokenRetrievalError> reply) {
  for (auto& callback : callbacks_) {
    std::move(callback).Run(reply);
  }
  callbacks_.clear();
}

// This implementation is for testing. It lets us manually control and simulate
// the moment a Gmail OTP is received for one-time passwords (OTP).
class FakeGmailOtpBackend : public one_time_tokens::GmailOtpBackend {
 public:
  using CallbackType = base::RepeatingCallback<void(
      base::expected<one_time_tokens::OneTimeToken,
                     one_time_tokens::OneTimeTokenRetrievalError>)>;

  FakeGmailOtpBackend() = default;
  ~FakeGmailOtpBackend() override = default;

  // one_time_tokens::GmailOtpBackend:
  one_time_tokens::ExpiringSubscription Subscribe(
      base::Time expiration,
      CallbackType callback) override {
    callbacks_.push_back(callback);
    return one_time_tokens::ExpiringSubscription();
  }

  one_time_tokens::ExpiringSubscription SubscribeToTickles(
      base::Time expiration,
      TickleCallback callback) override {
    return one_time_tokens::ExpiringSubscription();
  }

  // one_time_tokens::GmailOtpBackend:
  void OnIncomingOneTimeTokenBackendNotification(
      const one_time_tokens::OneTimeTokenBackendNotification& notification)
      override {}

  void FetchUserDataProcessingConsent(
      one_time_tokens::GmailOtpBackend::FetchUserDataProcessingConsentCallback
          callback) override {
    std::move(callback).Run(/*consent_states=*/std::nullopt);
  }

  // Simulates the reception of a Gmail OTP.
  void ProcessCallbacks(
      base::expected<one_time_tokens::OneTimeToken,
                     one_time_tokens::OneTimeTokenRetrievalError> reply) {
    for (auto& callback : callbacks_) {
      callback.Run(reply);
    }
    callbacks_.clear();
  }

  size_t num_callbacks() const { return callbacks_.size(); }

 private:
  std::vector<CallbackType> callbacks_;
};

// AutofillCrowdsourcingManager that classifies every field as a ONE_TIME_CODE.
class FakeAutofillCrowdsourcingManager : public AutofillCrowdsourcingManager {
 public:
  FakeAutofillCrowdsourcingManager(AutofillClient* autofill_client,
                                   version_info::Channel channel);
  ~FakeAutofillCrowdsourcingManager() override = default;

  bool StartQueryRequest(
      const std::vector<FormData>& forms,
      std::optional<net::IsolationInfo> isolation_info,
      base::OnceCallback<void(std::optional<QueryResponse>)> callback) override;

  bool StartUploadRequest(std::vector<AutofillUploadContents> upload_contents,
                          mojom::SubmissionSource form_submission_source,
                          bool is_password_manager_upload) override;
};

FakeAutofillCrowdsourcingManager::FakeAutofillCrowdsourcingManager(
    AutofillClient* autofill_client,
    version_info::Channel channel)
    : AutofillCrowdsourcingManager(autofill_client, channel) {}

bool FakeAutofillCrowdsourcingManager::StartQueryRequest(
    const std::vector<FormData>& forms,
    std::optional<net::IsolationInfo> isolation_info,
    base::OnceCallback<void(std::optional<QueryResponse>)> callback) {
  // Generate a response that classifies each field as a ONE_TIME_CODE field.
  std::vector<FormSignature> queried_form_signatures;
  AutofillQueryResponse response;
  for (const FormData& form : forms) {
    queried_form_signatures.push_back(CalculateFormSignature(form));
    auto* form_suggestion = response.add_form_suggestions();
    for (const FormFieldData& field : form.fields()) {
      auto* field_suggestion = form_suggestion->add_field_suggestions();
      field_suggestion->set_field_signature(
          CalculateFieldSignatureForField(field).value());
      *field_suggestion->add_predictions() =
          test::CreateFieldPrediction(ONE_TIME_CODE, /*is_override=*/false);
    }
  }

  std::move(callback).Run(AutofillCrowdsourcingManager::QueryResponse{
      base::Base64Encode(response.SerializeAsString()),
      queried_form_signatures});
  return true;
}

bool FakeAutofillCrowdsourcingManager::StartUploadRequest(
    std::vector<AutofillUploadContents> upload_contents,
    mojom::SubmissionSource form_submission_source,
    bool is_password_manager_upload) {
  // Overridden to prevent network communication from tests.
  return true;
}

class OtpTestAutofillManager : public BrowserAutofillManager {
 public:
  explicit OtpTestAutofillManager(ContentAutofillDriver* driver)
      : BrowserAutofillManager(driver) {
    test_api(*this).SetExternalDelegate(
        std::make_unique<TestAutofillExternalDelegate>(this));
  }
  ~OtpTestAutofillManager() override = default;

  [[nodiscard]] testing::AssertionResult WaitForFormsSeen(
      int min_num_awaited_calls) {
    return forms_seen_waiter_.Wait(min_num_awaited_calls);
  }

  [[nodiscard]] testing::AssertionResult WaitForSuggestionsShown(
      int min_num_awaited_calls) {
    return suggestions_shown_waiter_.Wait(min_num_awaited_calls);
  }

  TestAutofillExternalDelegate& external_delegate() {
    return *static_cast<TestAutofillExternalDelegate*>(
        test_api(*this).external_delegate());
  }

 private:
  TestAutofillManagerWaiter forms_seen_waiter_{
      *this,
      {AutofillManagerEvent::kFormsSeen}};

  TestAutofillManagerWaiter suggestions_shown_waiter_{
      *this,
      {AutofillManagerEvent::kAskForValuesToFill}};
};

class OtpTestAutofillClient : public TestContentAutofillClient {
 public:
  explicit OtpTestAutofillClient(content::WebContents* web_contents)
      : TestContentAutofillClient(web_contents) {
    set_crowdsourcing_manager(
        std::make_unique<FakeAutofillCrowdsourcingManager>(
            this, version_info::Channel::STABLE));
    set_sms_otp_backend(std::make_unique<FakeSmsOtpBackend>());
    gmail_otp_backend_ = std::make_unique<FakeGmailOtpBackend>();
    one_time_token_service_ =
        std::make_unique<one_time_tokens::OneTimeTokenServiceImpl>(
            GetSmsOtpBackend(), gmail_otp_backend_.get());
  }
  ~OtpTestAutofillClient() override = default;

  one_time_tokens::OneTimeTokenService* GetOneTimeTokenService()
      const override {
    return one_time_token_service_.get();
  }

  FakeSmsOtpBackend& sms_otp_backend() {
    return *static_cast<FakeSmsOtpBackend*>(GetSmsOtpBackend());
  }

  FakeGmailOtpBackend& gmail_otp_backend() {
    return *static_cast<FakeGmailOtpBackend*>(gmail_otp_backend_.get());
  }

 private:
  std::unique_ptr<one_time_tokens::GmailOtpBackend> gmail_otp_backend_;
  std::unique_ptr<one_time_tokens::OneTimeTokenServiceImpl>
      one_time_token_service_;
};

}  // namespace

class OtpManagerBrowserTest : public PlatformBrowserTest {
  void SetUpOnMainThread() override;

 protected:
  content::WebContents* web_contents() {
    return chrome_test_utils::GetActiveWebContents(this);
  }

  OtpTestAutofillManager& autofill_manager() {
    return *autofill_manager_injector_[web_contents()];
  }

  OtpTestAutofillClient& autofill_client() {
    return *autofill_client_injector_[web_contents()];
  }

  // The server communication is mocked out, so we don't need to disable it
  // in the test.
  test::AutofillBrowserTestEnvironment autofill_test_environment_{
      {.disable_server_communication = false}};

  TestAutofillClientInjector<OtpTestAutofillClient> autofill_client_injector_;
  TestAutofillManagerInjector<OtpTestAutofillManager>
      autofill_manager_injector_;
};

void OtpManagerBrowserTest::SetUpOnMainThread() {
  PlatformBrowserTest::SetUpOnMainThread();
  ASSERT_TRUE(embedded_test_server()->Start());
}

// Test fixture for the interaction between the WebOTP API and Autofill for
// OTPs. If the parameter is true, the WebOTP API should be invoked in the test.
class OtpManagerWithWebOtpApiBrowserTest
    : public OtpManagerBrowserTest,
      public testing::WithParamInterface<bool> {
 protected:
  bool is_webotp_api_used() const { return GetParam(); }
};

INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    OtpManagerWithWebOtpApiBrowserTest,
    ::testing::Bool());

// This integration test ensures that an OTP is correctly suggested by Autofill
// by default (when the WebOTP API is not used) but that an OTP does not get
// autofilled if a website calls the WebOTP API before.
IN_PROC_BROWSER_TEST_P(OtpManagerWithWebOtpApiBrowserTest,
                       SmsOtpAutofillIntegrationTest) {
  GURL url = embedded_test_server()->GetURL("/autofill/sms_otp_form.html");

  // Navigate to page and wait for form to be classified
  ASSERT_TRUE(chrome_test_utils::NavigateToURL(web_contents(), url));
  ASSERT_TRUE(autofill_manager().WaitForFormsSeen(1));

  // Immediately when a form field is classified as an OTP field, a subscription
  // for OTPs should be started if an SMS OTP backend exists.
  ASSERT_EQ(autofill_client().sms_otp_backend().num_callbacks(), 1u);

  // Using the WebOTP API should block the Autofill OTP feature.
  if (is_webotp_api_used()) {
    // Request an OTP via the WebOTP API. The JavaScript code returns true so
    // ExecJs does not wait for the resolution of the pending Promise.
    ASSERT_TRUE(ExecJs(web_contents(),
                       R"(navigator.credentials.get({
                              otp: {transport:['sms']}
                          });
                          true;)",
                       content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                       /*world_id=*/1));
    // As the WebOTP is asynchronous, wait until the use has been propagated.
    ASSERT_TRUE(base::test::RunUntil(
        [&] { return autofill_client().DocumentUsedWebOTP(); }));
    // From this point on, Autofill should not suggest OTPs anymore.
  }

  // Simulate an OTP arriving.
  autofill_client().sms_otp_backend().NotifyCallbacks(
      one_time_tokens::OneTimeToken(one_time_tokens::OneTimeTokenType::kSmsOtp,
                                    "123456", base::TimeTicks::Now()));

  // Simulate click on field.
  std::vector<const FormStructure*> forms =
      test_api(autofill_manager()).form_structures();
  ASSERT_EQ(forms.size(), 1u);
  const FormStructure& form = *forms.front();
  const AutofillField& first_field = *form.fields().front();
  autofill_manager().OnAskForValuesToFill(
      form.ToFormData(), first_field.global_id(), gfx::Rect(),
      AutofillSuggestionTriggerSource::kFormControlElementClicked,
      /*password_request=*/std::nullopt,
      autofill::AutofillManagerTestApi::pass_key());
  ASSERT_TRUE(autofill_manager().WaitForSuggestionsShown(1));

  // Verify expectations: The OTP should be suggested by autofill unless the
  // WebOTP API was called.
  const TestAutofillExternalDelegate& external_delegate =
      autofill_manager().external_delegate();
  const std::vector<Suggestion>& suggestions = external_delegate.suggestions();
  if (is_webotp_api_used()) {
    EXPECT_EQ(suggestions.size(), 0u);
  } else {
    ASSERT_EQ(suggestions.size(), 1u);
    EXPECT_EQ(suggestions[0].main_text.value, u"123456");
  }
}

}  // namespace autofill
