// Copyright 2025 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/glic/host/glic_ui.h"

#include <sstream>
#include <utility>

#include "base/scoped_observation.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/glic/host/glic.mojom-shared.h"
#include "chrome/browser/glic/public/features.h"
#include "chrome/browser/glic/test_support/interactive_glic_test.h"
#include "chrome/browser/glic/test_support/interactive_test_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/optimization_guide/core/feature_registry/feature_registration.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/state_observer.h"

namespace glic {

namespace {

using mojom::WebUiState;

class GlicUiStateHistoryObserver
    : public ui::test::StateObserver<std::vector<WebUiState>>,
      public Host::Observer {
 public:
  explicit GlicUiStateHistoryObserver(Host* host) : host_(*host) {
    states_.push_back(host->GetPrimaryWebUiState());
    host->AddObserver(this);
  }

  ~GlicUiStateHistoryObserver() override { host_->RemoveObserver(this); }

  ValueType GetStateObserverInitialState() const override { return states_; }

 private:
  void WebUiStateChanged(WebUiState state) override {
    states_.push_back(state);
    OnStateObserverStateChanged(states_);
  }

  const raw_ref<Host> host_;
  ValueType states_;
};

DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(GlicUiStateHistoryObserver,
                                    kGlicUiStateHistory);

class ContextAccessIndicatorObserver
    : public ui::test::StateObserver<std::vector<bool>> {
 public:
  explicit ContextAccessIndicatorObserver(GlicKeyedService* service) {
    scoped_subscription_ =
        service->AddContextAccessIndicatorStatusChangedCallback(
            base::BindRepeating(&ContextAccessIndicatorObserver::
                                    ContextAccessIndicatorStatusChanged,
                                base::Unretained(this)));
  }

  ~ContextAccessIndicatorObserver() override = default;

  ValueType GetStateObserverInitialState() const override { return states_; }

 private:
  void ContextAccessIndicatorStatusChanged(bool status) {
    states_.push_back(status);
    OnStateObserverStateChanged(states_);
  }

  base::CallbackListSubscription scoped_subscription_;
  ValueType states_;
};

DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(ContextAccessIndicatorObserver,
                                    kGlicContextAccessIndicatorHistory);

// Specifies artificial parameters for how network and loading should behave for
// tests in this file.
struct TestParams {
  TestParams() = default;
  explicit TestParams(bool connected) : start_connected(connected) {}
  TestParams(base::TimeDelta time_before_loading_page_,
             base::TimeDelta min_loading_page_duration_,
             base::TimeDelta max_loading_page_duration_,
             base::TimeDelta loading_delay_)
      : time_before_loading_page(time_before_loading_page_),
        min_loading_page_duration(min_loading_page_duration_),
        max_loading_page_duration(max_loading_page_duration_),
        loading_delay(loading_delay_) {}
  ~TestParams() = default;

  // Time before loading page shows.
  std::optional<base::TimeDelta> time_before_loading_page;
  // Minimum time loading page shows.
  std::optional<base::TimeDelta> min_loading_page_duration;
  // Maximum time loading page shows before error.
  std::optional<base::TimeDelta> max_loading_page_duration;
  // Time to load the glic UI.
  std::optional<base::TimeDelta> loading_delay;
  // Whether the page believes it has network at startup.
  bool start_connected = true;

  base::FieldTrialParams GetFieldTrialParams() const {
    base::FieldTrialParams params;
    if (time_before_loading_page) {
      params.emplace(
          features::kGlicPreLoadingTimeMs.name,
          base::StringPrintf("%u", time_before_loading_page->InMilliseconds()));
    }
    if (min_loading_page_duration) {
      params.emplace(features::kGlicMinLoadingTimeMs.name,
                     base::StringPrintf(
                         "%u", min_loading_page_duration->InMilliseconds()));
    }
    if (max_loading_page_duration) {
      params.emplace(features::kGlicMaxLoadingTimeMs.name,
                     base::StringPrintf(
                         "%u", max_loading_page_duration->InMilliseconds()));
    }
    return params;
  }
};

const ui::Accelerator escape_key(ui::VKEY_ESCAPE, ui::EF_NONE);

}  // namespace

// Base class that sets up network connection mode and timeouts based on
// `TestParams` (see above).
class GlicUiInteractiveUiTestBase : public test::InteractiveGlicTest {
 public:
  explicit GlicUiInteractiveUiTestBase(const TestParams& params)
      : InteractiveGlicTestMixin(params.GetFieldTrialParams()) {
    if (!params.start_connected) {
      GlicUI::simulate_no_connection_for_testing();
    }
    if (params.loading_delay) {
      std::ostringstream oss;
      oss << params.loading_delay->InMilliseconds();
      AddMockGlicQueryParam("delay_ms", oss.str());
    }
  }
  ~GlicUiInteractiveUiTestBase() override = default;

  auto CheckElementVisible(const DeepQuery& where, bool visible) {
    MultiStep steps;
    if (visible) {
      steps = InAnyContext(WaitForElementVisible(kGlicHostElementId, where));
    }
    steps += InAnyContext(CheckJsResultAt(
        kGlicHostElementId, where, "(el) => el.hidden", testing::Ne(visible)));
    AddDescriptionPrefix(steps, "CheckElementVisible");
    return steps;
  }

  auto CheckMockElementChecked(const DeepQuery& where, bool checked) {
    MultiStep steps = Steps(
        InAnyContext(WaitForElementVisible(kGlicContentsElementId, {"body"})),
        InAnyContext(CheckJsResultAt(kGlicContentsElementId, where,
                                     "(el) => el.checked",
                                     testing::Eq(checked))));
    AddDescriptionPrefix(steps, "CheckElementChecked");
    return steps;
  }

  auto WaitForMockElementChecked(const DeepQuery& where, bool checked) {
    return Do([this, where, checked]() {
      content::RenderFrameHost* frame = FindGlicGuestMainFrame();
      ASSERT_TRUE(frame);
      auto result =
          content::EvalJs(frame, content::JsReplace(R"js(
        (async () => {
          return new Promise((resolve) => {
            const check = () => {
              const el = document.querySelector($1);
              if (el && el.checked === $2) {
                resolve(true);
              } else {
                setTimeout(check, 100);
              }
            };
            check();
          });
        })()
      )js",
                                                    where[0], checked));
      EXPECT_EQ(true, result);
    });
  }

  auto CheckEscapeKeyDismisses(const DeepQuery& panel) {
    return InAnyContext(
        WaitForShow(kGlicHostElementId), CheckElementVisible(panel, true),
        InSameContext(SendAccelerator(kGlicHostElementId, escape_key)
                          .SetMustRemainVisible(false),
                      WaitForHide(kGlicViewElementId)),
        CheckControllerHasWidget(false));
  }

  auto ChangeConnectionState(bool online) {
    return ExecuteJs(kGlicHostElementId,
                     base::StringPrintf(R"(
        function () {
          const controller = window.appRouter.glicController;
          controller.simulateNoConnection = %s;
          controller.%s();
        }
      )",
                                        base::ToString(!online),
                                        online ? "online" : "offline"));
  }

  static auto CurrentStateMatcher(testing::Matcher<WebUiState> target) {
    return testing::AllOf(
        testing::Not(testing::IsEmpty()),
        testing::Property(&GlicUiStateHistoryObserver::ValueType::back,
                          target));
  }

  static auto IsCurrently(WebUiState state) {
    return CurrentStateMatcher(state);
  }

  static auto IsNotCurrently(WebUiState state) {
    return CurrentStateMatcher(testing::Ne(state));
  }

  static auto IsContextAccessIndicatorCurrently(bool showing) {
    return testing::AllOf(
        testing::Not(testing::IsEmpty()),
        testing::Property(&ContextAccessIndicatorObserver::ValueType::back,
                          showing));
  }

  const DeepQuery kOfflinePanel = {"#offlinePanel"};
  const DeepQuery kLoadingPanel = {"#loadingPanel"};
  const DeepQuery kErrorPanel = {"#errorPanel"};
  const DeepQuery kContentsPanel = {"#guestPanel"};
};

class GlicUiInteractiveTest : public GlicUiInteractiveUiTestBase {
 public:
  GlicUiInteractiveTest()
      : GlicUiInteractiveUiTestBase(TestParams(/*connected=*/true)) {}
  ~GlicUiInteractiveTest() override = default;
};

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiInteractiveTest, DISABLED_OpenGlicWindow) {
  base::HistogramTester histogram_tester;
  RunTestSequence(ObserveState(kGlicUiStateHistory, GetHost()),
                  OpenGlic(GlicInstrumentMode::kHostOnly));
  // The browser is active when opening the Glic window.
  histogram_tester.ExpectUniqueSample("Glic.Session.Open.BrowserActiveState",
                                      0 /*kBrowserActive*/, 1);
}

// Tests the network being connected at startup (as normal).
class GlicUiConnectedUiTest : public GlicUiInteractiveUiTestBase {
 public:
  GlicUiConnectedUiTest()
      : GlicUiInteractiveUiTestBase(TestParams(/*connected=*/true)) {}
  ~GlicUiConnectedUiTest() override = default;
};

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiConnectedUiTest,
                       DISABLED_DisconnectedPanelHidden) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsNotCurrently(WebUiState::kOffline)),
      CheckElementVisible(kOfflinePanel, false));
}

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiConnectedUiTest,
                       DISABLED_DoesNotHidePanelWhenReadyButOffline) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)),
      ChangeConnectionState(false), CheckElementVisible(kContentsPanel, true),
      CheckState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)));
}

// Skipping for kGlicMultiInstance.
IN_PROC_BROWSER_TEST_F(GlicUiConnectedUiTest,
                       DISABLED_CanAttachWithBrowserWindow) {
  RunTestSequence(OpenGlic(GlicInstrumentMode::kHostAndContents),
                  CheckMockElementChecked({"#canAttachCheckbox"}, true));
}

// TODO(crbug.com/454087646): Not reliable yet.
IN_PROC_BROWSER_TEST_F(GlicUiConnectedUiTest,
                       CanNotAttachWithMinimizedBrowser) {
  RunTestSequence(
      OpenGlic(GlicInstrumentMode::kHostAndContents), Detach(),
      WaitForMockElementChecked({"#canAttachCheckbox"}, true), Do([&]() {
        BrowserView::GetBrowserViewForBrowser(browser())->Minimize();
      }),
      WaitForMockElementChecked({"#canAttachCheckbox"}, false));
}

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiConnectedUiTest,
                       DISABLED_DoesNotNavigateToUnsupportedOrigin) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostAndContents),
      WaitForElementVisible(kGlicContentsElementId, {"body"}),
      InAnyContext(ExecuteJs(kGlicContentsElementId,
                             R"js(()=>{location = 'http://b.test/page';})js")),
      // Just wait a bit and make sure the page doesn't navigate.
      InAnyContext(CheckJsResult(kGlicContentsElementId, R"js(
  ()=>{
    const {promise, resolve} = Promise.withResolvers();
    window.setTimeout(() => resolve(true), 1000);
    return promise;
  })js")));
}

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiConnectedUiTest,
                       DISABLED_HidesTabAccessUIOnWebClientCrash) {
  content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      ObserveState(kGlicContextAccessIndicatorHistory, glic_service()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostAndContents),
      WaitForElementVisible(kGlicContentsElementId, {"body"}),
      InAnyContext(ExecuteJs(
          kGlicContentsElementId,
          R"js(()=>{client.browser.setContextAccessIndicator(true);})js")),
      InAnyContext(WaitForState(kGlicContextAccessIndicatorHistory,
                                IsContextAccessIndicatorCurrently(true))),
      // Kills the web client process, simulating a renderer crash.
      Do([this] { FindGlicGuestMainFrame()->GetProcess()->Shutdown(0); }),
      InAnyContext(
          WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kError))),
      InAnyContext(WaitForState(kGlicContextAccessIndicatorHistory,
                                IsContextAccessIndicatorCurrently(false))));
}

// Tests the network being unavailable at startup.
class GlicUiDisconnectedUiTest : public GlicUiInteractiveUiTestBase {
 public:
  GlicUiDisconnectedUiTest()
      : GlicUiInteractiveUiTestBase(TestParams(/*connected=*/false)) {
    feature_list_.InitAndDisableFeature(features::kGlicIgnoreOfflineState);
  }
  ~GlicUiDisconnectedUiTest() override = default;
  base::test::ScopedFeatureList feature_list_;
};

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiDisconnectedUiTest,
                       DISABLED_DisconnectedPanelShown) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kOffline)),
      CheckElementVisible(kOfflinePanel, true));
}

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiDisconnectedUiTest, DISABLED_LoadsWhenBackOnline) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      ChangeConnectionState(true),
      WaitForState(kGlicUiStateHistory, IsNotCurrently(WebUiState::kOffline)),
      WaitForElementVisible(kGlicHostElementId, kContentsPanel),
      CheckElementVisible(kOfflinePanel, false),
      CheckState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)));
}

// Tests the entire loading sequence through to error if Glic is slow to load.
class GlicUiFullLoadingSequenceTest : public GlicUiInteractiveUiTestBase {
 public:
  GlicUiFullLoadingSequenceTest()
      : GlicUiInteractiveUiTestBase(
            TestParams(base::Milliseconds(250),  // Pre-load
                       base::Milliseconds(250),  // Min loading time
                       base::Milliseconds(500),  // Max loading time
                       base::Hours(1)))          // Actual loading time
  {}
  ~GlicUiFullLoadingSequenceTest() override = default;
};

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiFullLoadingSequenceTest, DISABLED_Test) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kError)),
      CheckElementVisible(kErrorPanel, true),
      CheckElementVisible(kContentsPanel, false),
      CheckState(kGlicUiStateHistory,
                 testing::ElementsAre(
                     WebUiState::kUninitialized, WebUiState::kBeginLoad,
                     WebUiState::kShowLoading, WebUiState::kFinishLoading,
                     WebUiState::kError)));
}

// Tests the loading sequence where Glic loads almost immediately and there is
// no hold.
class GlicUiQuickLoadingSequenceNoHoldTest
    : public GlicUiInteractiveUiTestBase {
 public:
  GlicUiQuickLoadingSequenceNoHoldTest()
      : GlicUiInteractiveUiTestBase(
            TestParams(base::Milliseconds(250),  // Pre-load
                       base::Milliseconds(0),    // Min loading time
                       base::Hours(1),           // Max loading time
                       base::Seconds(1)))        // Actual loading time
  {}
  ~GlicUiQuickLoadingSequenceNoHoldTest() override = default;
};

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiQuickLoadingSequenceNoHoldTest, DISABLED_Test) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)),
      CheckElementVisible(kContentsPanel, true),
      CheckState(kGlicUiStateHistory,
                 testing::ElementsAre(
                     WebUiState::kUninitialized, WebUiState::kBeginLoad,
                     WebUiState::kShowLoading, WebUiState::kFinishLoading,
                     WebUiState::kReady)));
}

// Tests the loading sequence where Glic loads almost immediately and there is a
// hold.
class GlicUiQuickLoadingSequenceWithHoldTest
    : public GlicUiInteractiveUiTestBase {
 public:
  GlicUiQuickLoadingSequenceWithHoldTest()
      : GlicUiInteractiveUiTestBase(
            TestParams(base::Milliseconds(0),     // Pre-load
                       base::Seconds(5),          // Min loading time
                       base::Hours(1),            // Max loading time
                       base::Milliseconds(500)))  // Actual loading time
  {}
  ~GlicUiQuickLoadingSequenceWithHoldTest() override = default;
};

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiQuickLoadingSequenceWithHoldTest, DISABLED_Test) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)),
      CheckElementVisible(kContentsPanel, true),
      CheckState(
          kGlicUiStateHistory,
          testing::ElementsAre(WebUiState::kUninitialized,
                               WebUiState::kBeginLoad, WebUiState::kShowLoading,
                               WebUiState::kHoldLoading, WebUiState::kReady)));
}

// Tests the loading sequence where Glic loads almost immediately during
// preload.
class GlicUiQuickLoadingSequenceWithPreloadTest
    : public GlicUiInteractiveUiTestBase {
 public:
  GlicUiQuickLoadingSequenceWithPreloadTest()
      : GlicUiInteractiveUiTestBase(
            TestParams(base::Seconds(3),         // Pre-load
                       base::Seconds(5),         // Min loading time
                       base::Seconds(10),        // Max loading time
                       base::Milliseconds(10)))  // Actual loading time
  {}
  ~GlicUiQuickLoadingSequenceWithPreloadTest() override = default;
};

// TODO(crbug.com/418639389): these probably need to be broken into unit
// tests with only integration tests for the messaging being passed back and
// forth; slow test runners can cause any time limit to be overrun.
IN_PROC_BROWSER_TEST_F(GlicUiQuickLoadingSequenceWithPreloadTest,
                       DISABLED_Test) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)),
      CheckElementVisible(kContentsPanel, true),
      CheckState(
          kGlicUiStateHistory,
          testing::ElementsAre(WebUiState::kUninitialized,
                               WebUiState::kBeginLoad, WebUiState::kReady)));
}

// Tests the loading panel is visible during load.
class GlicUiLoadingPanelWaitingTest : public GlicUiInteractiveUiTestBase {
 public:
  GlicUiLoadingPanelWaitingTest()
      : GlicUiInteractiveUiTestBase(
            TestParams(base::Milliseconds(0),  // Pre-load
                       base::Milliseconds(0),  // Min loading time
                       base::Hours(1),         // Max loading time
                       base::Hours(1)))        // Actual loading time
  {}
  ~GlicUiLoadingPanelWaitingTest() override = default;
};

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelWaitingTest, DISABLED_Test) {
  RunTestSequence(ObserveState(kGlicUiStateHistory, GetHost()),
                  DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                                           GlicInstrumentMode::kHostOnly),
                  WaitForState(kGlicUiStateHistory,
                               IsCurrently(WebUiState::kFinishLoading)),
                  CheckElementVisible(kLoadingPanel, true));
}

// Tests the loading panel is visible during hold.
class GlicUiLoadingPanelHoldingTest : public GlicUiInteractiveUiTestBase {
 public:
  GlicUiLoadingPanelHoldingTest()
      : GlicUiInteractiveUiTestBase(
            TestParams(base::Milliseconds(0),     // Pre-load
                       base::Hours(1),            // Min loading time
                       base::Hours(2),            // Max loading time
                       base::Milliseconds(500)))  // Actual loading time
  {}
  ~GlicUiLoadingPanelHoldingTest() override = default;
};

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelHoldingTest, DISABLED_Test) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kHoldLoading)),
      CheckElementVisible(kLoadingPanel, true));
}

// Test that the escape key can be used to dismiss the floaty window in various
// loading and error states.

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiDisconnectedUiTest, DISABLED_EscapeKeyDismisses) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kOffline)),
      CheckEscapeKeyDismisses(kOfflinePanel));
}

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelWaitingTest,
                       DISABLED_EscapeKeyDismisses) {
  RunTestSequence(ObserveState(kGlicUiStateHistory, GetHost()),
                  DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                                           GlicInstrumentMode::kHostOnly),
                  WaitForState(kGlicUiStateHistory,
                               IsCurrently(WebUiState::kFinishLoading)),
                  CheckEscapeKeyDismisses(kLoadingPanel));
}

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelHoldingTest,
                       DISABLED_EscapeKeyDismisses) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kHoldLoading)),
      CheckEscapeKeyDismisses(kLoadingPanel));
}

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiFullLoadingSequenceTest,
                       DISABLED_EscapeKeyDismisses) {
  RunTestSequence(
      ObserveState(kGlicUiStateHistory, GetHost()),
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly),
      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kError)),
      CheckEscapeKeyDismisses(kErrorPanel));
}

#if !BUILDFLAG(IS_CHROMEOS)
// Multi-profile is not supported on ChromeOS.
class GlicWithMultipleProfilesTest : public GlicUiInteractiveUiTestBase {
 public:
  GlicWithMultipleProfilesTest() : GlicUiInteractiveUiTestBase({}) {}
  ~GlicWithMultipleProfilesTest() override = default;

  BrowserWindowInterface* CreateBrowserWithNewProfile() {
    ProfileManager* profile_manager = g_browser_process->profile_manager();
    base::FilePath new_path =
        profile_manager->GenerateNextProfileDirectoryPath();
    Profile& new_profile =
        profiles::testing::CreateProfileSync(profile_manager, new_path);

    return CreateBrowser(&new_profile);
  }
};

// Creates two browsers with different profiles. Opens glic in each and verifies
// it loads, doesn't crash, and hides the other glic window.
// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicWithMultipleProfilesTest,
                       DISABLED_OpenGlicInEachProfile) {
  BrowserWindowInterface* first_browser = browser();
  BrowserWindowInterface* second_browser = CreateBrowserWithNewProfile();
  SetActiveBrowser(second_browser);

  RunTestSequence(
      // Warning!: `kAttached` really just clicks the glic button, the window
      // will open in detached mode because `features::kGlicDetached` is
      // enabled. We do this because InteractiveGlicTestMixin::ToggleGlicWindow
      // doesn't work right in detached mode with multiple profiles.
      // TODO(b/418284946): Fix ToggleGlicWindow.
      DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                               GlicInstrumentMode::kHostOnly));

  SetActiveBrowser(first_browser);
  RunTestSequence(CheckControllerShowing(false),
                  DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                                           GlicInstrumentMode::kHostOnly));

  SetActiveBrowser(second_browser);
  RunTestSequence(CheckControllerShowing(false),
                  DeprecatedOpenGlicWindow(GlicWindowMode::kAttached,
                                           GlicInstrumentMode::kHostOnly));
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(ui::test::PollingStateObserver<GURL>,
                                    kOpenedTabUrlState);

class GlicApiUiRedirectTest : public test::InteractiveGlicTest,
                              public testing::WithParamInterface<bool> {
 public:
  void SetUp() override {
    admin_hostname_ =
        GetParam() ? "admin.google.com" : "access.workspace.google.com";

    embedded_https_test_server().SetCertHostnames(
        {admin_hostname_, "gemini.google.com", "127.0.0.1"});
    embedded_https_test_server().AddDefaultHandlers();
    ASSERT_TRUE(embedded_https_test_server().InitializeAndListen());

    GURL admin_url_base = embedded_https_test_server().GetURL("/echo?");
    GURL::Replacements replacements;
    replacements.SetHostStr(admin_hostname_);
    GURL admin_url = admin_url_base.ReplaceComponents(replacements);

    SetGlicPagePath("/server-redirect-302");
    AddMockGlicQueryParam(admin_url.spec());

    replacements.SetHostStr("gemini.google.com");
    destination_url_ =
        embedded_https_test_server().GetURL("/echo").ReplaceComponents(
            replacements);

    GURL::Replacements pattern_replacements;
    pattern_replacements.SetPathStr("/echo");
    pattern_replacements.SetQueryStr("*");

    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {features::kGlicDebugWebview, {}},
        {features::kGlicCaaGuestError,
         {{"glic-caa-link-url", destination_url_.spec()},
          {"glic-caa-redirect-patterns",
           admin_url.ReplaceComponents(pattern_replacements).spec()}}}};

    redirect_features_.InitWithFeaturesAndParameters(enabled_features,
                                                     /*disabled_features=*/{});
    test::InteractiveGlicTest::SetUp();
  }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule(admin_hostname_, "127.0.0.1");
    host_resolver()->AddRule("gemini.google.com", "127.0.0.1");

    test::InteractiveGlicTest::SetUpOnMainThread();
  }
  const GURL& destination_url() { return destination_url_; }

 protected:
  auto WaitForTabOpenedTo(int tab, GURL url) {
    return Steps(
        PollState(kOpenedTabUrlState,
                  [this, tab]() {
                    auto* const model = browser()->tab_strip_model();
                    auto* tab_at_index = model->GetTabAtIndex(tab);
                    if (!tab_at_index) {
                      return GURL();
                    }
                    return tab_at_index->GetContents()->GetVisibleURL();
                  }),
        WaitForState(kOpenedTabUrlState, url),
        StopObservingState(kOpenedTabUrlState));
  }

  base::UserActionTester& user_action_tester() { return user_action_tester_; }

 private:
  GURL destination_url_;
  std::string admin_hostname_;
  base::UserActionTester user_action_tester_;
  base::test::ScopedFeatureList redirect_features_;
};

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_P(GlicApiUiRedirectTest, DISABLED_AccessDeniedAdmin) {
  auto https_server_running =
      embedded_https_test_server().StartAcceptingConnectionsAndReturnHandle();

  RunTestSequence(
      OpenGlic(GlicInstrumentMode::kHostOnly),
      InAnyContext(WaitForElementVisible(
          kGlicHostElementId, {"#disabledByAdminPanel:not([hidden])"})),
      CheckTabCount(1),
      InAnyContext(WaitForElementVisible(kGlicHostElementId,
                                         {"#disabledByAdminPanel .notice a"})),
      ClickElement(kGlicHostElementId, {"#disabledByAdminPanel .notice a"})
          .SetContext(ui::InteractionSequence::ContextMode::kAny)
          .SetMustRemainVisible(false),
      InAnyContext(Do([&]() {
        EXPECT_EQ(user_action_tester().GetActionCount(
                      "Glic.DisabledByAdminPanelLinkClicked"),
                  1);
      })),
      WaitForTabOpenedTo(1, destination_url()), CheckControllerShowing(false));
}

INSTANTIATE_TEST_SUITE_P(All, GlicApiUiRedirectTest, ::testing::Bool());

// TODO(b/453696965): Broken in multi-instance.
IN_PROC_BROWSER_TEST_F(GlicUiConnectedUiTest,
                       DISABLED_AccessDeniedAdminWithoutLink) {
  RunTestSequence(
      OpenGlic(GlicInstrumentMode::kHostOnly), InAnyContext(Do([&]() {
        browser()->GetProfile()->GetPrefs()->SetInteger(
            optimization_guide::prefs::kGeminiSettings,
            std::to_underlying(optimization_guide::prefs::
                                   GeminiSettingsPolicyState::kDisabled));
      })),

      InAnyContext(WaitForElementVisible(
          kGlicHostElementId, {"#disabledByAdminPanel:not(.show-disabled-"
                               "by-admin-link) .without-link"})),
      InAnyContext(
          EnsureNotVisible(kGlicHostElementId, {"#disabledByAdminPanel a"})));
}

}  // namespace glic
