// 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 "base/test/test_future.h"
#include "chrome/browser/web_applications/scheduler/manifest_silent_update_result.h"
#include "chrome/browser/web_applications/test/fake_web_app_origin_association_manager.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/fake_web_contents_manager.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_origin_association_manager.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_scope.h"
#include "chrome/common/chrome_features.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace web_app {

namespace {

class WebAppScopeExtensionsTest : public WebAppTest {
 public:
  WebAppScopeExtensionsTest() = default;
  ~WebAppScopeExtensionsTest() override = default;

  void SetUp() override {
    WebAppTest::SetUp();
    auto fake_association_manager =
        std::make_unique<FakeWebAppOriginAssociationManager>(*profile());
    fake_association_manager->set_pass_through(true);
    fake_provider().SetOriginAssociationManager(
        std::move(fake_association_manager));
    test::AwaitStartWebAppProviderAndSubsystems(profile());
  }

 protected:
  webapps::AppId InstallWebAppWithScope(const GURL& scope) {
    SetupPageNoScopeExtensions(scope);
    return test::InstallForWebContents(
        profile(), web_contents(),
        webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON);
  }

  webapps::AppId InstallWebAppWithScopeExtensions(
      const GURL& scope,
      std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions) {
    SetupPageWithScopeExtensions(scope, std::move(scope_extensions));
    return test::InstallForWebContents(
        profile(), web_contents(),
        webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON);
  }

  FakeWebContentsManager& web_contents_manager() {
    return static_cast<FakeWebContentsManager&>(
        fake_provider().web_contents_manager());
  }

  void SetupPageNoScopeExtensions(const GURL& url) {
    web_contents_manager().SetUrlLoaded(web_contents(), url);
    web_contents_manager().CreateBasicInstallPageState(
        /*install_url=*/url,
        /*manifest_url=*/GURL("https://www.example.com/manifest.json"),
        /*start_url=*/url);
  }

  void SetupPageWithScopeExtensions(
      const GURL& url,
      std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions) {
    SetupPageNoScopeExtensions(url);
    auto& page_state = web_contents_manager().GetOrCreatePageState(url);
    page_state.manifest_before_default_processing->scope_extensions =
        std::move(scope_extensions);
  }

  WebAppRegistrar& registrar() { return fake_provider().registrar_unsafe(); }
};

TEST_F(WebAppScopeExtensionsTest, TestScopeNotifiedOnReinstall) {
  const GURL scope("https://example.com/");
  webapps::AppId app_id = InstallWebAppWithScope(scope);

  // Re-install the app with extended scope, and verify that the scope observer
  // is notified.
  WebAppTestRegistryObserverAdapter adapter(
      &fake_provider().registrar_unsafe());
  base::test::TestFuture<const webapps::AppId&, const WebAppScope&> future;
  adapter.SetWebAppEffectiveScopeChangedDelegate(future.GetRepeatingCallback());
  std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions;
  scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
      url::Origin::Create(GURL("https://example.org")),
      /*has_origin_wildcard=*/false));
  scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
      url::Origin::Create(GURL("https://example.net")),
      /*has_origin_wildcard=*/true));
  InstallWebAppWithScopeExtensions(scope, std::move(scope_extensions));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(app_id, future.Get<webapps::AppId>());

  // Now re-install without the scope extensions, and verify that the scope
  // observer is notified.
  future.Clear();
  InstallWebAppWithScope(scope);
  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(app_id, future.Get<webapps::AppId>());
}

TEST_F(WebAppScopeExtensionsTest, TestScopeNotifiedOnAddedUpdate) {
  const GURL scope("https://example.com/");
  webapps::AppId app_id = InstallWebAppWithScope(scope);

  // Update the app with extended scope, and verify that the scope observer
  // is notified.
  WebAppTestRegistryObserverAdapter adapter(
      &fake_provider().registrar_unsafe());
  base::test::TestFuture<const webapps::AppId&, const WebAppScope&> future;
  adapter.SetWebAppEffectiveScopeChangedDelegate(future.GetRepeatingCallback());

  // Do the update.
  std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions;
  scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
      url::Origin::Create(GURL("https://example.org")),
      /*has_origin_wildcard=*/false));
  scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
      url::Origin::Create(GURL("https://example.net")),
      /*has_origin_wildcard=*/true));
  SetupPageWithScopeExtensions(scope, std::move(scope_extensions));
  base::test::TestFuture<ManifestSilentUpdateCompletionInfo>
      manifest_silent_update_future;
  fake_provider().scheduler().ScheduleManifestSilentUpdate(
      *web_contents(), /*previous_time_for_silent_icon_update=*/std::nullopt,
      manifest_silent_update_future.GetCallback());
  ASSERT_TRUE(manifest_silent_update_future.Wait());
  EXPECT_EQ(manifest_silent_update_future.Take().result,
            ManifestSilentUpdateCheckResult::kAppSilentlyUpdated);

  // Wait for the scope change to be observed.
  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(app_id, future.Get<webapps::AppId>());
  // Double check the scope extensions are there.
  WebAppScope effective_scope = future.Get<WebAppScope>();
  EXPECT_TRUE(effective_scope.IsInScope(GURL("https://example.org")));
  EXPECT_TRUE(effective_scope.IsInScope(GURL("https://example.net")));
  EXPECT_TRUE(effective_scope.IsInScope(GURL("https://sub.example.net")));
}

TEST_F(WebAppScopeExtensionsTest, TestScopeNotifiedOnRemovedUpdate) {
  const GURL scope("https://example.com/");
  std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions;
  scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
      url::Origin::Create(GURL("https://example.org")),
      /*has_origin_wildcard=*/false));
  scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
      url::Origin::Create(GURL("https://example.net")),
      /*has_origin_wildcard=*/true));
  const webapps::AppId app_id =
      InstallWebAppWithScopeExtensions(scope, std::move(scope_extensions));

  // Update the app with no extensions, and verify that the scope observer
  // is notified.
  WebAppTestRegistryObserverAdapter adapter(
      &fake_provider().registrar_unsafe());
  base::test::TestFuture<const webapps::AppId&, const WebAppScope&> future;
  adapter.SetWebAppEffectiveScopeChangedDelegate(future.GetRepeatingCallback());

  // Do the update.
  SetupPageNoScopeExtensions(scope);
  base::test::TestFuture<ManifestSilentUpdateCompletionInfo>
      manifest_silent_update_future;
  fake_provider().scheduler().ScheduleManifestSilentUpdate(
      *web_contents(), /*previous_time_for_silent_icon_update=*/std::nullopt,
      manifest_silent_update_future.GetCallback());
  ASSERT_TRUE(manifest_silent_update_future.Wait());
  EXPECT_EQ(manifest_silent_update_future.Take().result,
            ManifestSilentUpdateCheckResult::kAppSilentlyUpdated);

  // Wait for the scope change to be observed.
  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(app_id, future.Get<webapps::AppId>());
  // Double check the scope extensions are gone.
  WebAppScope effective_scope = future.Get<WebAppScope>();
  EXPECT_FALSE(effective_scope.IsInScope(GURL("https://example.org")));
  EXPECT_FALSE(effective_scope.IsInScope(GURL("https://example.net")));
  EXPECT_FALSE(effective_scope.IsInScope(GURL("https://sub.example.net")));
}

}  // namespace

}  // namespace web_app
