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

#import "base/test/metrics/histogram_tester.h"
#import "ios/web/navigation/navigation_manager_impl.h"
#import "ios/web/public/test/navigation_test_util.h"
#import "ios/web/public/test/web_state_test_util.h"
#import "ios/web/public/test/web_view_content_test_util.h"
#import "ios/web/test/web_int_test.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/web_state_impl.h"
#import "net/test/embedded_test_server/default_handlers.h"
#import "net/test/embedded_test_server/embedded_test_server.h"

namespace web {

// Test fixture for navigation manager tests requiring a real webview (for
// example, those involving session restoration).
class NavigationManagerImplTest : public WebIntTest {
 protected:
  void SetUp() override {
    WebIntTest::SetUp();

    test_server_ = std::make_unique<net::test_server::EmbeddedTestServer>();
    net::test_server::RegisterDefaultHandlers(test_server_.get());
    ASSERT_TRUE(test_server_->Start());
  }

  NavigationManager* navigation_manager() {
    return web_state()->GetNavigationManager();
  }

  NavigationManagerImpl& navigation_manager_impl() {
    return web::WebStateImpl::FromWebState(web_state())
        ->GetNavigationManagerImpl();
  }

  base::HistogramTester histogram_tester_;

 protected:
  // Embedded test server which hosts sample pages.
  std::unique_ptr<net::EmbeddedTestServer> test_server_;
};

// Tests state of an empty navigation manager.
TEST_F(NavigationManagerImplTest, EmptyManager) {
  ASSERT_EQ(0, navigation_manager()->GetItemCount());
}

// Tests state of a single navigation.
TEST_F(NavigationManagerImplTest, SingleNavigation) {
  ASSERT_EQ(0, navigation_manager()->GetItemCount());

  GURL url = test_server_->GetURL("/echo");
  ASSERT_TRUE(LoadUrl(url));

  EXPECT_EQ(1, navigation_manager()->GetItemCount());
}

// Tests state after restoration of a single item.
TEST_F(NavigationManagerImplTest, SingleItemRestore) {
  ASSERT_EQ(0, navigation_manager()->GetItemCount());

  GURL url = test_server_->GetURL("/echo");
  auto item = std::make_unique<NavigationItemImpl>();
  item->SetURL(url);
  item->SetTitle(u"Test Website");

  std::vector<std::unique_ptr<NavigationItem>> items;
  items.push_back(std::move(item));

  navigation_manager()->Restore(/*last_committed_item_index=*/0,
                                std::move(items));

  EXPECT_EQ(1, navigation_manager()->GetItemCount());
  EXPECT_EQ(0, navigation_manager()->GetLastCommittedItemIndex());

  NavigationItem* visible_item = navigation_manager()->GetVisibleItem();
  ASSERT_TRUE(visible_item);
  NavigationItem* last_committed_item =
      navigation_manager()->GetLastCommittedItem();
  ASSERT_TRUE(last_committed_item);
  EXPECT_EQ(visible_item, last_committed_item);
  ASSERT_FALSE(navigation_manager()->GetPendingItem());
}

// Tests state after restoration of multiple items.
TEST_F(NavigationManagerImplTest, MultipleItemRestore) {
  ASSERT_EQ(0, web_state()->GetNavigationManager()->GetItemCount());

  auto item0 = std::make_unique<NavigationItemImpl>();
  item0->SetURL(test_server_->GetURL("/echo?0"));
  item0->SetTitle(u"Test Website 0");
  auto item1 = std::make_unique<NavigationItemImpl>();
  item1->SetURL(test_server_->GetURL("/echo?1"));

  std::vector<std::unique_ptr<NavigationItem>> items;
  items.push_back(std::move(item0));
  items.push_back(std::move(item1));

  navigation_manager()->Restore(1 /* last_committed_item_index */,
                                std::move(items));

  ASSERT_EQ(2, web_state()->GetNavigationManager()->GetItemCount());

  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
  ASSERT_FALSE(navigation_manager()->GetPendingItem());
}

// Tests that restoring session replaces existing history in navigation manager.
TEST_F(NavigationManagerImplTest, RestoreSessionResetsHistory) {
  EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
  EXPECT_EQ(-1, navigation_manager()->GetLastCommittedItemIndex());

  ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?0")));
  ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?1")));
  navigation_manager_impl().AddPendingItem(
      test_server_->GetURL("/echo?0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
  EXPECT_TRUE(navigation_manager()->GetPendingItem() != nullptr);

  // Sets up each test case with a session history of 3 items. The middle item
  // is the current item.
  auto item0 = std::make_unique<NavigationItemImpl>();
  GURL url0 = test_server_->GetURL("/echo?restored0");
  item0->SetURL(url0);
  auto item1 = std::make_unique<NavigationItemImpl>();
  GURL url1 = test_server_->GetURL("/echo?restored1");
  item1->SetURL(url1);
  auto item2 = std::make_unique<NavigationItemImpl>();
  GURL url2 = test_server_->GetURL("/echo?restored2");
  item2->SetURL(url2);
  std::vector<std::unique_ptr<NavigationItem>> items;
  items.push_back(std::move(item0));
  items.push_back(std::move(item1));
  items.push_back(std::move(item2));

  navigation_manager()->Restore(2 /* last_committed_item_index */,
                                std::move(items));
  ASSERT_EQ(3, web_state()->GetNavigationManager()->GetItemCount());

  EXPECT_EQ(2, navigation_manager()->GetLastCommittedItemIndex());
  EXPECT_TRUE(navigation_manager()->GetPendingItem() == nullptr);

  //  // Check that cached visible item is returned.
  EXPECT_EQ(url2, navigation_manager()->GetVisibleItem()->GetURL());
}

// Tests that Reload from detached mode restores cached history.
TEST_F(NavigationManagerImplTest, DetachedModeReload) {
  GURL url0 = test_server_->GetURL("/echo?0");
  ASSERT_TRUE(LoadUrl(url0));
  GURL url1 = test_server_->GetURL("/echo?1");
  ASSERT_TRUE(LoadUrl(url1));
  GURL url2 = test_server_->GetURL("/echo?2");
  ASSERT_TRUE(LoadUrl(url2));

  navigation_manager()->GoBack();

  ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForPageLoadTimeout, ^{
        base::RunLoop().RunUntilIdle();
        return url1 == navigation_manager()->GetVisibleItem()->GetURL();
      }));

  ASSERT_EQ(3, web_state()->GetNavigationManager()->GetItemCount());

  // Clear the webview.
  navigation_manager_impl().DetachFromWebView();
  [web::test::GetWebController(web_state()) removeWebView];

  // Reloading should restore history.
  navigation_manager()->Reload(web::ReloadType::NORMAL,
                               false /* check_for_repost */);
  EXPECT_EQ(url1, navigation_manager()->GetVisibleItem()->GetURL());

  std::vector<NavigationItem*> back_items =
      navigation_manager()->GetBackwardItems();
  ASSERT_EQ(1ul, back_items.size());
  EXPECT_EQ(url0, back_items[0]->GetURL().spec());

  std::vector<NavigationItem*> forward_items =
      navigation_manager()->GetForwardItems();
  ASSERT_EQ(1ul, forward_items.size());
  EXPECT_EQ(url2, forward_items[0]->GetURL().spec());

  histogram_tester_.ExpectTotalCount(kRestoreNavigationItemCount, 1);
  histogram_tester_.ExpectBucketCount(kRestoreNavigationItemCount, 3, 1);
}

// Tests that GoToIndex from detached mode restores cached history with
// updated / current item offset.
TEST_F(NavigationManagerImplTest, DetachedModeGoToIndex) {
  GURL url0 = test_server_->GetURL("/echo?0");
  ASSERT_TRUE(LoadUrl(url0));
  ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?1")));
  ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?2")));

  navigation_manager_impl().DetachFromWebView();
  [web::test::GetWebController(web_state()) removeWebView];

  navigation_manager()->GoToIndex(0);

  EXPECT_EQ(nullptr, navigation_manager()->GetPendingItem());
  EXPECT_EQ(url0, navigation_manager()->GetVisibleItem()->GetURL());

  histogram_tester_.ExpectTotalCount(kRestoreNavigationItemCount, 1);
  histogram_tester_.ExpectBucketCount(kRestoreNavigationItemCount, 3, 1);
}

// Tests that LoadIfNecessary from detached mode restores cached history.
TEST_F(NavigationManagerImplTest, DetachedModeLoadIfNecessary) {
  ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?0")));
  ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?1")));
  GURL url2 = test_server_->GetURL("/echo?2");
  ASSERT_TRUE(LoadUrl(url2));

  navigation_manager_impl().DetachFromWebView();
  [web::test::GetWebController(web_state()) removeWebView];

  navigation_manager()->LoadIfNecessary();

  EXPECT_EQ(nullptr, navigation_manager()->GetPendingItem());
  EXPECT_EQ(url2, navigation_manager()->GetVisibleItem()->GetURL());

  histogram_tester_.ExpectTotalCount(kRestoreNavigationItemCount, 1);
  histogram_tester_.ExpectBucketCount(kRestoreNavigationItemCount, 3, 1);
}

// Tests that LoadURLWithParams from detached mode restores backward history and
// adds the new item at the end.
TEST_F(NavigationManagerImplTest, DetachedModeLoadURLWithParams) {
  ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?0")));
  ASSERT_TRUE(LoadUrl(test_server_->GetURL("/echo?1")));

  navigation_manager_impl().DetachFromWebView();
  [web::test::GetWebController(web_state()) removeWebView];

  GURL url = test_server_->GetURL("/echo?2");
  NavigationManager::WebLoadParams params(url);
  navigation_manager()->LoadURLWithParams(params);

  EXPECT_EQ(nullptr, navigation_manager()->GetPendingItem());
  EXPECT_EQ(url, navigation_manager()->GetVisibleItem()->GetURL());

  histogram_tester_.ExpectTotalCount(kRestoreNavigationItemCount, 1);
  histogram_tester_.ExpectBucketCount(kRestoreNavigationItemCount, 3, 1);
}

}  // namespace web
