// Copyright 2017 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/sessions/session_restore_observer.h"

#include <memory>
#include <unordered_map>

#include "base/run_loop.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resource_coordinator/tab_load_tracker_test_support.h"
#include "chrome/browser/resource_coordinator/tab_manager.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/sessions/session_restore_test_helper.h"
#include "chrome/browser/sessions/session_service_factory.h"
#include "chrome/browser/sessions/session_service_test_helper.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

using content::WebContents;
using content::NavigationHandle;

class MockSessionRestoreObserver : public SessionRestoreObserver {
 public:
  MockSessionRestoreObserver() { SessionRestore::AddObserver(this); }

  MockSessionRestoreObserver(const MockSessionRestoreObserver&) = delete;
  MockSessionRestoreObserver& operator=(const MockSessionRestoreObserver&) =
      delete;

  ~MockSessionRestoreObserver() { SessionRestore::RemoveObserver(this); }

  enum class SessionRestoreEvent { kStartedLoadingTabs, kFinishedLoadingTabs };

  const std::vector<SessionRestoreEvent>& session_restore_events() const {
    return session_restore_events_;
  }

  // SessionRestoreObserver implementation:
  void OnSessionRestoreStartedLoadingTabs() override {
    session_restore_events_.emplace_back(
        SessionRestoreEvent::kStartedLoadingTabs);
  }
  void OnSessionRestoreFinishedLoadingTabs() override {
    session_restore_events_.emplace_back(
        SessionRestoreEvent::kFinishedLoadingTabs);
  }

 private:
  std::vector<SessionRestoreEvent> session_restore_events_;
};

class SessionRestoreObserverTest : public InProcessBrowserTest {
 protected:
  SessionRestoreObserverTest() = default;

  SessionRestoreObserverTest(const SessionRestoreObserverTest&) = delete;
  SessionRestoreObserverTest& operator=(const SessionRestoreObserverTest&) =
      delete;

  void SetUpOnMainThread() override {
    SessionStartupPref pref(SessionStartupPref::LAST);
    SessionStartupPref::SetStartupPref(browser()->GetProfile(), pref);
#if BUILDFLAG(IS_CHROMEOS)
    SessionServiceTestHelper helper(
        SessionServiceFactory::GetForProfile(browser()->GetProfile()));
    helper.SetForceBrowserNotAliveWithNoWindows(true);
#endif
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  BrowserWindowInterface* QuitBrowserAndRestore(
      BrowserWindowInterface* browser) {
    Profile* const profile = browser->GetProfile();

    auto keep_alive = std::make_unique<ScopedKeepAlive>(
        KeepAliveOrigin::SESSION_RESTORE, KeepAliveRestartOption::DISABLED);
    auto profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>(
        profile, ProfileKeepAliveOrigin::kBrowserWindow);
    CloseBrowserSynchronously(browser);

    // Create a new window, which should trigger session restore.
    chrome::NewEmptyWindow(profile);
    SessionRestoreTestHelper().Wait();
    return GetLastActiveBrowserWindowInterfaceWithAnyProfile();
  }

  void WaitForTabsToLoad(TabStripModel* tab_strip_model) {
    for (int i = 0; i < tab_strip_model->count(); ++i) {
      WebContents* contents = tab_strip_model->GetWebContentsAt(i);
      contents->GetController().LoadIfNecessary();
      resource_coordinator::WaitForTransitionToLoaded(contents);
    }
  }

  GURL GetTestURL() const {
    return embedded_test_server()->GetURL("/title1.html");
  }

  const std::vector<MockSessionRestoreObserver::SessionRestoreEvent>&
  session_restore_events() const {
    return mock_observer_.session_restore_events();
  }

  size_t number_of_session_restore_events() const {
    return session_restore_events().size();
  }

  MockSessionRestoreObserver& session_restore_observer() {
    return mock_observer_;
  }

 private:
  MockSessionRestoreObserver mock_observer_;
};

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_SingleTabSessionRestore DISABLED_SingleTabSessionRestore
#else
#define MAYBE_SingleTabSessionRestore SingleTabSessionRestore
#endif
IN_PROC_BROWSER_TEST_F(SessionRestoreObserverTest,
                       MAYBE_SingleTabSessionRestore) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));

  // The restored browser should have 1 tab.
  TabStripModel* tab_strip =
      QuitBrowserAndRestore(browser())->GetTabStripModel();
  ASSERT_TRUE(tab_strip);
  ASSERT_EQ(1, tab_strip->count());

  ASSERT_EQ(1u, number_of_session_restore_events());
  EXPECT_EQ(
      MockSessionRestoreObserver::SessionRestoreEvent::kStartedLoadingTabs,
      session_restore_events()[0]);

  ASSERT_NO_FATAL_FAILURE(WaitForTabsToLoad(tab_strip));
  ASSERT_EQ(2u, number_of_session_restore_events());
  EXPECT_EQ(
      MockSessionRestoreObserver::SessionRestoreEvent::kFinishedLoadingTabs,
      session_restore_events()[1]);
}

IN_PROC_BROWSER_TEST_F(SessionRestoreObserverTest, MultipleTabSessionRestore) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), GetTestURL(), WindowOpenDisposition::NEW_BACKGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);

  // The restored browser should have 2 tabs.
  TabStripModel* tab_strip =
      QuitBrowserAndRestore(browser())->GetTabStripModel();
  ASSERT_TRUE(tab_strip);
  ASSERT_EQ(2, tab_strip->count());

  ASSERT_EQ(1u, number_of_session_restore_events());
  EXPECT_EQ(
      MockSessionRestoreObserver::SessionRestoreEvent::kStartedLoadingTabs,
      session_restore_events()[0]);

  ASSERT_NO_FATAL_FAILURE(WaitForTabsToLoad(tab_strip));
  ASSERT_EQ(2u, number_of_session_restore_events());
  EXPECT_EQ(
      MockSessionRestoreObserver::SessionRestoreEvent::kFinishedLoadingTabs,
      session_restore_events()[1]);
}
