// 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.

#import "ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.h"

#import "base/memory/weak_ptr.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/task_environment.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_item_configuration.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_item_type.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper_observer.h"
#import "ios/chrome/browser/contextual_panel/sample/model/sample_panel_model.h"
#import "ios/chrome/browser/contextual_panel/utils/contextual_panel_metrics.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "testing/platform_test.h"

namespace {

/// Fake ContextualPanelTabHelperObserver for test.
class TestContextualPanelTabHelperObserver
    : public ContextualPanelTabHelperObserver {
 public:
  void ContextualPanelHasNewData(
      ContextualPanelTabHelper* tab_helper,
      std::vector<base::WeakPtr<ContextualPanelItemConfiguration>>
          item_configurations) override {
    item_configurations_set_ = true;
    item_configurations_ = item_configurations;
    run_loop_->Quit();
  }

  void ContextualPanelTabHelperDestroyed(
      ContextualPanelTabHelper* tab_helper) override {
    tab_helper->RemoveObserver(this);
  }

  bool item_configurations_set_ = false;
  raw_ptr<base::RunLoop> run_loop_;
  std::vector<base::WeakPtr<ContextualPanelItemConfiguration>>
      item_configurations_;
};

}  // namespace

// Unittests related to the ContextualPanelTabHelper.
class ContextualPanelTabHelperTest : public PlatformTest {
 public:
  ContextualPanelTabHelperTest() {}
  ~ContextualPanelTabHelperTest() override {}

  void SetUp() override {
    observer_.run_loop_ = &run_loop_;
    sample_model_ = std::make_unique<SamplePanelModel>();

    std::map<ContextualPanelItemType,
             raw_ptr<ContextualPanelModel>>
        models;
    models.emplace(ContextualPanelItemType::SamplePanelItem,
                   sample_model_.get());
    ContextualPanelTabHelper::CreateForWebState(&web_state_, models);

    ContextualPanelTabHelper()->AddObserver(&observer_);
  }

  void TearDown() override {
    ContextualPanelTabHelper()->RemoveObserver(&observer_);
  }

  ContextualPanelTabHelper* ContextualPanelTabHelper() {
    return ContextualPanelTabHelper::FromWebState(&web_state_);
  }

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  base::RunLoop run_loop_;
  // sample_model_ must be declared before web_state_ so that it is destroyed
  // after the web_state_ (and its owned ContextualPanelTabHelper), preventing
  // the tab helper from holding a dangling raw_ptr during teardown.
  std::unique_ptr<SamplePanelModel> sample_model_;
  web::FakeWebState web_state_;
  TestContextualPanelTabHelperObserver observer_;
};

// Tests that the tab helper observer disconnects before the tab helper is
// destroyed. The observers emptiness DCHECK would trigger if that was not the
// case.
TEST_F(ContextualPanelTabHelperTest, TestObserverIsAlertedOnDestroyed) {
  web_state_.CloseWebState();
}

// Tests that the tab helper calls any observers when a web navigation starts.
TEST_F(ContextualPanelTabHelperTest, TestObserverIsAlertedOnNavigationStarted) {
  EXPECT_FALSE(observer_.item_configurations_set_);

  web::FakeNavigationContext context;
  web_state_.OnNavigationStarted(&context);

  run_loop_.Run();

  EXPECT_TRUE(observer_.item_configurations_set_);
  EXPECT_EQ(0u, observer_.item_configurations_.size());
}

// Tests that the tab helper queries the models and calls any observer when
// a web navigation finishes.
TEST_F(ContextualPanelTabHelperTest,
       TestObserverIsAlertedOnNavigationFinished) {
  base::HistogramTester tester;

  web::FakeNavigationContext context;
  web_state_.OnNavigationFinished(&context);

  run_loop_.Run();

  EXPECT_EQ(1u, observer_.item_configurations_.size());
  tester.ExpectUniqueSample(
      "IOS.ContextualPanel.Model.InfoBlocksWithContentCount", 1, 1);
  tester.ExpectBucketCount(
      "IOS.ContextualPanel.Model.Relevance.SamplePanelItem",
      ModelRelevanceType::High, 1);

  tester.ExpectUniqueTimeSample(
      "IOS.ContextualPanel.SamplePanelItem.ModelResponseTime", base::Seconds(0),
      1);
}

// Tests that data is only refreshed for navigations that are not same document.
TEST_F(ContextualPanelTabHelperTest, ShouldRefreshData) {
  // Test with a different document navigation.
  web::FakeNavigationContext different_document_context;
  different_document_context.SetIsSameDocument(false);
  EXPECT_TRUE(ContextualPanelTabHelper()->ShouldRefreshData(
      &web_state_, &different_document_context));

  // Test with a same document navigation.
  web::FakeNavigationContext same_document_context;
  same_document_context.SetIsSameDocument(true);
  EXPECT_FALSE(ContextualPanelTabHelper()->ShouldRefreshData(
      &web_state_, &same_document_context));
}
