// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <memory>
#import <string>

#import "base/functional/bind.h"
#import "base/run_loop.h"
#import "base/test/ios/wait_util.h"
#import "ios/web/grit/ios_web_resources.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/test/navigation_test_util.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/public/test/web_view_content_test_util.h"
#import "ios/web/public/test/web_view_interaction_test_util.h"
#import "ios/web/public/webui/web_ui_ios_controller.h"
#import "ios/web/public/webui/web_ui_ios_controller_factory.h"
#import "ios/web/public/webui/web_ui_ios_data_source.h"
#import "ios/web/test/grit/test_resources.h"
#import "ios/web/test/test_url_constants.h"
#import "ios/web/web_state/web_state_impl.h"
#import "url/gurl.h"
#import "url/scheme_host_port.h"

using base::test::ios::kWaitForPageLoadTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using web::test::TapWebViewElementWithId;
using web::test::WaitForWebViewContainingText;

namespace web {

namespace {

// Hostname for test WebUI page.
const char kTestWebUIURLHost[] = "testwebui";
const char kTestWebUIURLHost2[] = "testwebui2";

// The element id of the link to load a webUI page.
const char kWebUIPageLinkID[] = "link";
// Text present on the sample WebUI page.
const char kWebUIPageText[] = "WebUI page";

// Controller for test WebUI.
class TestUI : public WebUIIOSController {
 public:
  // Constructs controller from `web_ui` and `ui_handler` which will communicate
  // with test WebUI page.
  TestUI(WebUIIOS* web_ui, const std::string& host, int resource_id)
      : WebUIIOSController(web_ui, host) {
    web::WebUIIOSDataSource* source = web::WebUIIOSDataSource::Create(host);

    source->SetDefaultResource(resource_id);

    web::WebState* web_state = web_ui->GetWebState();
    web::WebUIIOSDataSource::Add(web_state->GetBrowserState(), source);
  }

  ~TestUI() override = default;
};

// Factory that creates TestUI controller.
class TestWebUIControllerFactory : public WebUIIOSControllerFactory {
 public:
  // Constructs a controller factory.
  TestWebUIControllerFactory() {}

  // WebUIIOSControllerFactory overrides.
  std::unique_ptr<WebUIIOSController> CreateWebUIIOSControllerForURL(
      WebUIIOS* web_ui,
      const GURL& url) const override {
    if (!url.SchemeIs(kTestWebUIScheme)) {
      return nullptr;
    }
    if (url.GetHost() == kTestWebUIURLHost) {
      return std::make_unique<TestUI>(web_ui, url.GetHost(),
                                      IDR_WEBUI_TEST_HTML);
    }
    DCHECK_EQ(url.GetHost(), kTestWebUIURLHost2);
    return std::make_unique<TestUI>(web_ui, url.GetHost(),
                                    IDR_WEBUI_TEST_HTML_2);
  }

  NSInteger GetErrorCodeForWebUIURL(const GURL& url) const override {
    if (url.SchemeIs(kTestWebUIScheme)) {
      return 0;
    }
    return NSURLErrorUnsupportedURL;
  }
};
}  // namespace

// A test fixture for verifying WebUI.
class WebUITest : public WebTestWithWebState {
 protected:
  WebUITest() : WebTestWithWebState() {}

  void SetUp() override {
    WebTestWithWebState::SetUp();
    factory_ = std::make_unique<TestWebUIControllerFactory>();
    WebUIIOSControllerFactory::RegisterFactory(factory_.get());

    url::SchemeHostPort tuple(kTestWebUIScheme, kTestWebUIURLHost, 0);
    GURL url(tuple.Serialize());
    test::LoadUrl(web_state(), url);

    // LoadIfNecessary is needed because the view is not created (but needed)
    // when loading the page. TODO(crbug.com/41309809): Remove this call.
    web_state()->GetNavigationManager()->LoadIfNecessary();

    ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
      base::RunLoop().RunUntilIdle();
      return !web_state()->IsLoading();
    }));

    ASSERT_EQ(url.spec(), BaseUrl());
  }

  void TearDown() override {
    WebUIIOSControllerFactory::DeregisterFactory(factory_.get());
    WebTestWithWebState::TearDown();
  }

 private:
  std::unique_ptr<TestWebUIControllerFactory> factory_;
};

// Tests that a web UI page is loaded and that the WebState correctly reports
// `WebStateImpl::HasWebUI`.
TEST_F(WebUITest, LoadWebUIPage) {
  ASSERT_TRUE(WebStateImpl::FromWebState(web_state())->HasWebUI());

  EXPECT_TRUE(WaitForWebViewContainingText(web_state(), kWebUIPageText));
}

// Tests that a web UI page is correctly loaded when navigated to from another
// webUI page.
TEST_F(WebUITest, LoadWebUIPageLoadViaLinkClick) {
  ASSERT_TRUE(TapWebViewElementWithId(web_state(), kWebUIPageLinkID));

  url::SchemeHostPort tuple(kTestWebUIScheme, kTestWebUIURLHost2, 0);
  GURL url(tuple.Serialize());

  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
    base::RunLoop().RunUntilIdle();
    return !web_state()->IsLoading() && url.spec() == BaseUrl();
  }));

  ASSERT_TRUE(WebStateImpl::FromWebState(web_state())->HasWebUI());
}

}  // namespace web
