// Copyright 2018 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/complex_tasks/task_tab_helper.h"

#include <string>

#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/sessions/content/navigation_task_id.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

class MockTaskTabHelper : public tasks::TaskTabHelper {
 public:
  static void CreateForWebContents(content::WebContents* contents) {
    DCHECK(contents);
    if (!FromWebContents(contents))
      contents->SetUserData(UserDataKey(),
                            base::WrapUnique(new MockTaskTabHelper(contents)));
  }

  static MockTaskTabHelper* FromWebContents(content::WebContents* contents) {
    DCHECK(contents);
    return static_cast<MockTaskTabHelper*>(
        contents->GetUserData(UserDataKey()));
  }

  explicit MockTaskTabHelper(content::WebContents* web_contents)
      : tasks::TaskTabHelper(web_contents) {}

  friend class TaskTabHelperUnitTest;
};

class TaskTabHelperUnitTest : public ChromeRenderViewHostTestHarness {
 protected:
  const std::string kSearchDomain = "http://www.google.com/";
  const GURL kSearchURL = GURL(kSearchDomain);

  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    MockTaskTabHelper::CreateForWebContents(web_contents());
    task_tab_helper_ = MockTaskTabHelper::FromWebContents(web_contents());
    NavigateAndCommit(kSearchURL);
  }

  void TearDown() override {
    task_tab_helper_ = nullptr;
    ChromeRenderViewHostTestHarness::TearDown();
  }

  void GoBack() { content::NavigationSimulator::GoBack(web_contents()); }

  void GoBackNTimes(int times) {
    while (times--) {
      GoBack();
    }
  }

  void NavigateAndCommitNTimes(int times) {
    while (times--) {
      static int unique_int = 0;
      // Note: The URLs need to be different on each iteration. Otherwise,
      // navigations will be treated as reloads and will not create a new
      // NavigationEntry.
      NavigateAndCommit(
          GURL(kSearchDomain + base::NumberToString(++unique_int)));
    }
  }

  content::NavigationEntry* GetLastCommittedEntry() {
    return web_contents()->GetController().GetLastCommittedEntry();
  }

  raw_ptr<MockTaskTabHelper> task_tab_helper_;
};

TEST_F(TaskTabHelperUnitTest, TestGetCurrentTaskId) {
  std::unique_ptr<content::WebContents> test_parent_web_contents(
      content::WebContentsTester::CreateTestWebContents(
          web_contents()->GetBrowserContext(), nullptr));
  GURL parent_gurl("http://parent.com");
  content::WebContentsTester::For(test_parent_web_contents.get())
      ->NavigateAndCommit(parent_gurl);

  content::NavigationEntry* navigation_entry =
      test_parent_web_contents->GetController().GetLastCommittedEntry();
  sessions::NavigationTaskId* navigation_task_id =
      sessions::NavigationTaskId::Get(navigation_entry);
  navigation_task_id->set_id(3);
  navigation_task_id->set_root_id(4);
  EXPECT_EQ(
      tasks::TaskTabHelper::GetCurrentTaskId(test_parent_web_contents.get())
          ->id(),
      3);
  EXPECT_EQ(
      tasks::TaskTabHelper::GetCurrentTaskId(test_parent_web_contents.get())
          ->root_id(),
      4);
}

TEST_F(TaskTabHelperUnitTest, TestTaskIdExistingChain) {
  NavigateAndCommit(GURL("http://a.com"));
  sessions::NavigationTaskId a_navigation_task_id =
      *sessions::NavigationTaskId::Get(GetLastCommittedEntry());
  NavigateAndCommit(GURL("http://b.com"));
  sessions::NavigationTaskId b_navigation_task_id =
      *sessions::NavigationTaskId::Get(GetLastCommittedEntry());

  EXPECT_EQ(b_navigation_task_id.parent_id(), a_navigation_task_id.id());
  EXPECT_EQ(a_navigation_task_id.root_id(), b_navigation_task_id.root_id());
}

TEST_F(TaskTabHelperUnitTest, TestTaskIdNewChain) {
  NavigateAndCommit(GURL("http://a.com"));
  sessions::NavigationTaskId a_navigation_task_id =
      *sessions::NavigationTaskId::Get(GetLastCommittedEntry());

  NavigateAndCommit(GURL("http://b.com"), ui::PAGE_TRANSITION_TYPED);
  sessions::NavigationTaskId b_navigation_task_id =
      *sessions::NavigationTaskId::Get(GetLastCommittedEntry());

  EXPECT_EQ(b_navigation_task_id.parent_id(), -1);
  EXPECT_NE(a_navigation_task_id.root_id(), b_navigation_task_id.root_id());
}

TEST_F(TaskTabHelperUnitTest, TestTaskIdBackButton) {
  NavigateAndCommit(GURL("http://a.com"), ui::PAGE_TRANSITION_TYPED);
  NavigateAndCommit(GURL("http://b.com"));
  sessions::NavigationTaskId b_navigation_task_id =
      *sessions::NavigationTaskId::Get(GetLastCommittedEntry());
  GoBack();
  sessions::NavigationTaskId a_navigation_task_id =
      *sessions::NavigationTaskId::Get(GetLastCommittedEntry());

  // A should still have no parent after a back navigation and
  // shouldn't link to B (like it would if we navigated a.com -> b.com -> a.com
  // via clicking links)
  EXPECT_EQ(a_navigation_task_id.parent_id(), -1);
  EXPECT_NE(a_navigation_task_id.parent_id(), b_navigation_task_id.id());
}

TEST_F(TaskTabHelperUnitTest, TestTaskIdBackViaLink) {
  NavigateAndCommit(GURL("http://a.com"), ui::PAGE_TRANSITION_TYPED);
  NavigateAndCommit(GURL("http://b.com"));
  sessions::NavigationTaskId b_navigation_task_id =
      *sessions::NavigationTaskId::Get(GetLastCommittedEntry());
  NavigateAndCommit(GURL("http://a.com"));
  sessions::NavigationTaskId a_navigation_task_id =
      *sessions::NavigationTaskId::Get(GetLastCommittedEntry());

  // We got back to a.com via a link (not back button) so it should now point to
  // B.
  EXPECT_NE(a_navigation_task_id.parent_id(), -1);
  EXPECT_EQ(a_navigation_task_id.parent_id(), b_navigation_task_id.id());
}
