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

#ifndef COMPONENTS_USER_EDUCATION_TEST_FEATURE_PROMO_CONTROLLER_TEST_BASE_H_
#define COMPONENTS_USER_EDUCATION_TEST_FEATURE_PROMO_CONTROLLER_TEST_BASE_H_

#include <map>
#include <memory>
#include <optional>

#include "base/feature_list.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/task_environment.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/feature_engagement/test/mock_tracker.h"
#include "components/user_education/common/feature_promo/feature_promo_controller.h"
#include "components/user_education/common/feature_promo/feature_promo_registry.h"
#include "components/user_education/common/feature_promo/feature_promo_result.h"
#include "components/user_education/common/feature_promo/feature_promo_session_policy.h"
#include "components/user_education/common/feature_promo/impl/feature_promo_controller_impl.h"
#include "components/user_education/common/help_bubble/help_bubble_factory_registry.h"
#include "components/user_education/common/tutorial/tutorial_registry.h"
#include "components/user_education/common/tutorial/tutorial_service.h"
#include "components/user_education/common/user_education_context.h"
#include "components/user_education/common/user_education_storage_service.h"
#include "components/user_education/product_messaging/product_messaging_controller.h"
#include "components/user_education/test/mock_user_education_context.h"
#include "components/user_education/test/test_help_bubble.h"
#include "components/user_education/test/test_user_education_storage_service.h"
#include "components/user_education/test/user_education_session_mocks.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_test_util.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/events/event_modifiers.h"

namespace user_education::test {

BASE_DECLARE_FEATURE(kTestIPHFocusHelpBubbleScreenReaderPromoFeature);

// Base test fixture that can be used to test a FeaturePromoController. Mocks or
// fakes all of the other systems the controller needs to talk to.
class FeaturePromoControllerTestBase : public testing::Test {
 public:
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kAnchorElementId);
  static const ui::ElementContext kAnchorElementContext;
  static constexpr char kTestFocusHelpBubbleAcceleratorPromoRead[] =
      "test_focus_help_bubble_accelerator_promo_read";

  FeaturePromoControllerTestBase();
  ~FeaturePromoControllerTestBase() override;

  void SetUp() override;
  void TearDown() override;

 protected:
  // Base class for test wrappers (see below); allows setting/getting help
  // bubble contexts.
  class TestPromoControllerBase {
   public:
    TestPromoControllerBase();
    virtual ~TestPromoControllerBase();

    void set_context_for_help_bubble(ui::ElementIdentifier id,
                                     UserEducationContextPtr context) {
      bubble_contexts_[id] = context;
    }

    UserEducationContextPtr get_context_for_help_bubble(
        ui::ElementIdentifier id) const {
      const auto it = bubble_contexts_.find(id);
      return it != bubble_contexts_.end() ? it->second : nullptr;
    }

   private:
    std::map<ui::ElementIdentifier, UserEducationContextPtr> bubble_contexts_;
  };

  // Wrapper for a promo controller that implements all the application-specific
  // methods.
  template <class T>
    requires std::derived_from<T, FeaturePromoControllerImpl>
  class TestPromoController : public T, public TestPromoControllerBase {
   public:
    using T::T;
    ~TestPromoController() override = default;

   protected:
    std::u16string GetBodyIconAltText() const override {
      return u"Body Icon Alt Text";
    }
    const base::Feature* GetScreenReaderPromptPromoFeature() const override {
      return &kTestIPHFocusHelpBubbleScreenReaderPromoFeature;
    }
    const char* GetScreenReaderPromptPromoEventName() const override {
      return kTestFocusHelpBubbleAcceleratorPromoRead;
    }
    std::u16string GetTutorialScreenReaderHint(
        const ui::AcceleratorProvider*) const override {
      return u"Tutorial Screen Reader Hint";
    }
    std::u16string GetFocusHelpBubbleScreenReaderHint(
        FeaturePromoSpecification::PromoType,
        ui::TrackedElement*,
        const ui::AcceleratorProvider*) const override {
      return u"Focus Help Bubble Screen Reader Hint";
    }

    UserEducationContextPtr GetContextForHelpBubble(
        const ui::TrackedElement* anchor_element) const override {
      return get_context_for_help_bubble(anchor_element->identifier());
    }
  };

  // Sets the mock tracker's initialization success.
  virtual std::optional<bool> GetTrackerResult() const;
  void SendTrackerResult(bool result);

  // Finds the current help bubble.
  TestHelpBubble* GetHelpBubble(
      std::optional<ui::ElementContext> context = std::nullopt) const;

  feature_engagement::test::MockTracker& tracker() { return tracker_; }
  FeaturePromoRegistry& promo_registry() { return promo_registry_; }
  HelpBubbleFactoryRegistry& help_bubble_factory_registry() {
    return help_bubble_factory_registry_;
  }
  UserEducationStorageService& storage_service() { return storage_service_; }
  MockFeaturePromoSessionPolicy& session_policy() { return session_policy_; }
  TutorialService& tutorial_service() { return tutorial_service_; }
  ProductMessagingController& messaging_controller() {
    return messaging_controller_;
  }
  FeaturePromoControllerImpl& promo_controller() { return *promo_controller_; }
  const scoped_refptr<MockUserEducationContext>& promo_context() {
    return test_promo_context_;
  }
  ui::test::TestElement& anchor_element() { return anchor_element_; }

  // Implemented by derived classes to create the controller.
  virtual std::unique_ptr<FeaturePromoControllerImpl> CreateController() = 0;

 private:
  class TestTutorialService : public TutorialService {
   public:
    using TutorialService::TutorialService;
    ~TestTutorialService() override;

   protected:
    // TutorialService:
    std::u16string GetBodyIconAltText(bool is_last_step) const override;
  };

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
  feature_engagement::test::MockTracker tracker_;
  FeaturePromoRegistry promo_registry_;
  TestUserEducationStorageService storage_service_;

  // Have a default anchor element that starts visible, to make things easier.
  // Tests can create their own elements in this context or others.
  ui::test::TestElement anchor_element_{kAnchorElementId,
                                        kAnchorElementContext};

  // Don't use a completely faked tutorial service for now; it's sufficient to
  // use the help bubble factory with test help bubbles instead.
  HelpBubbleFactoryRegistry help_bubble_factory_registry_;
  TutorialRegistry tutorial_registry_;
  TestTutorialService tutorial_service_{&tutorial_registry_,
                                        &help_bubble_factory_registry_};

  // Use a vanilla product messaging controller since the messages themselves
  // can be mocked. The associated session provider will never start a new
  // session since that is not being tested.
  TestUserEducationSessionProvider session_provider_{false};
  ProductMessagingController messaging_controller_;

  MockFeaturePromoSessionPolicy session_policy_;
  std::unique_ptr<FeaturePromoControllerImpl> promo_controller_;
  scoped_refptr<MockUserEducationContext> test_promo_context_;
  std::vector<feature_engagement::Tracker::OnInitializedCallback>
      on_initialized_callbacks_;
};

}  // namespace user_education::test

#endif  // COMPONENTS_USER_EDUCATION_TEST_FEATURE_PROMO_CONTROLLER_TEST_BASE_H_
