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

#import "ios/chrome/test/app/chrome_test_util.h"

#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "base/ios/ios_util.h"
#import "base/test/ios/wait_util.h"
#import "base/time/time.h"
#import "components/crash/core/common/reporter_running_ios.h"
#import "components/metrics/metrics_pref_names.h"
#import "components/metrics/metrics_service.h"
#import "components/prefs/pref_member.h"
#import "components/previous_session_info/previous_session_info.h"
#import "components/previous_session_info/previous_session_info_private.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/app/application_delegate/metrics_mediator.h"
#import "ios/chrome/app/application_delegate/metrics_mediator_testing.h"
#import "ios/chrome/app/main_application_delegate_testing.h"
#import "ios/chrome/app/main_controller.h"
#import "ios/chrome/app/profile/profile_state.h"
#import "ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/main/ui/browser_layout_view_controller.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_controller.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_controller_testing.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/country_code_picker_commands.h"
#import "ios/chrome/browser/shared/public/commands/drive_file_picker_commands.h"
#import "ios/chrome/browser/shared/public/commands/unit_conversion_commands.h"
#import "ios/chrome/browser/shared/ui/chrome_overlay_window/chrome_overlay_window.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/common/crash_report/crash_helper.h"
#import "ios/chrome/test/app/tab_test_util.h"
#import "ios/web/public/navigation/navigation_context.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state_observer.h"
#import "net/base/apple/url_conversions.h"

// A subclass to pass instances of UIOpenURLContext to scene delegate during
// testing. UIOpenURLContext has no init available, so this can only be
// allocated. It uses obscuring properties for URL and options.
// TODO(crbug.com/40711105) Explore improving this which can become brittle.
@interface FakeUIOpenURLContext : UIOpenURLContext
@property(nonatomic, copy) NSURL* URL;
@property(nonatomic, strong) UISceneOpenURLOptions* options;
@end

@implementation FakeUIOpenURLContext
@synthesize URL = _URL;
@synthesize options = _options;
@end

namespace {

inline constexpr base::TimeDelta kProfileCreationTimeout = base::Seconds(20);

// Returns the original ProfileIOS if `incognito` is false. If
// `incognito` is true, returns an off-the-record ProfileIOS.
ProfileIOS* GetProfile(bool incognito) {
  // The profile can take time to initialize when the app starts (e.g.,
  // enterprise registration blocking pref initialization).
  CHECK(base::test::ios::WaitUntilConditionOrTimeout(kProfileCreationTimeout, ^{
    return chrome_test_util::GetForegroundActiveScene().profileState.profile !=
           nil;
  }));
  ProfileIOS* profile =
      chrome_test_util::GetForegroundActiveScene().profileState.profile;
  CHECK(profile);
  CHECK(!profile->IsOffTheRecord());

  return incognito ? profile->GetOffTheRecordProfile() : profile;
}

// When present, stores a browser that will override the one from SceneState.
Browser* gMainBrowserOverride;

}  // namespace

namespace chrome_test_util {

MainController* GetMainController() {
  return MainApplicationDelegate.sharedMainController;
}

SceneState* GetForegroundActiveScene() {
  return MainApplicationDelegate.sharedAppState.foregroundActiveScene;
}

SceneController* GetForegroundActiveSceneController() {
  return MainApplicationDelegate.sharedAppState.foregroundActiveScene
      .controller;
}

NSUInteger RegularBrowserCount() {
  return static_cast<NSUInteger>(
      BrowserListFactory::GetForProfile(GetOriginalProfile())
          ->BrowsersOfType(BrowserList::BrowserType::kRegularAndInactive)
          .size());
}

ProfileIOS* GetOriginalProfile() {
  return GetProfile(false);
}

ProfileIOS* GetCurrentIncognitoProfile() {
  return GetProfile(true);
}

void SetMainBrowserOverride(Browser* browser) {
  gMainBrowserOverride = browser;
}

Browser* GetMainBrowser() {
  if (gMainBrowserOverride) {
    return gMainBrowserOverride;
  }
  return GetForegroundActiveScene()
      .browserProviderInterface.mainBrowserProvider.browser;
}

Browser* GetCurrentBrowser() {
  return GetForegroundActiveScene()
      .browserProviderInterface.currentBrowserProvider.browser;
}

UIViewController* GetActiveViewController() {
  UIWindow* main_window = GetAnyKeyWindow();
  DCHECK([main_window isKindOfClass:[ChromeOverlayWindow class]]);
  UIViewController* main_view_controller = main_window.rootViewController;

  // The active view controller is either the TabGridViewController or its
  // presented BVC. The BVC is itself contained inside of a
  // BrowserLayoutViewController.
  UIViewController* active_view_controller =
      main_view_controller.presentedViewController
          ? main_view_controller.presentedViewController
          : main_view_controller;
  if ([active_view_controller
          isKindOfClass:[BrowserLayoutViewController class]]) {
    active_view_controller =
        base::apple::ObjCCastStrict<BrowserLayoutViewController>(
            active_view_controller)
            .browserViewController;
  }
  return active_view_controller;
}

id<SceneCommands,
   BrowserCommands,
   BrowserCoordinatorCommands,
   CountryCodePickerCommands,
   UnitConversionCommands,
   DriveFilePickerCommands>
HandlerForActiveBrowser() {
  return static_cast<id<SceneCommands, BrowserCommands,
                        BrowserCoordinatorCommands, UnitConversionCommands,
                        CountryCodePickerCommands, DriveFilePickerCommands>>(
      GetMainBrowser()->GetCommandDispatcher());
}

void RemoveAllInfoBars() {
  web::WebState* webState = GetCurrentWebState();
  if (webState) {
    infobars::InfoBarManager* info_bar_manager =
        InfoBarManagerImpl::FromWebState(webState);
    if (info_bar_manager) {
      info_bar_manager->RemoveAllInfoBars(false /* animate */);
    }
  }
}

void ClearPresentedState(ProceduralBlock completion) {
  [GetForegroundActiveSceneController()
      dismissModalDialogsWithCompletion:completion
                         dismissOmnibox:YES];
}

void SetBooleanLocalStatePref(const char* pref_name, bool value) {
  DCHECK(GetApplicationContext());
  DCHECK(GetApplicationContext()->GetLocalState());
  BooleanPrefMember pref;
  pref.Init(pref_name, GetApplicationContext()->GetLocalState());
  pref.SetValue(value);
}

void SetBooleanUserPref(ProfileIOS* profile,
                        const char* pref_name,
                        bool value) {
  DCHECK(profile);
  DCHECK(profile->GetPrefs());
  BooleanPrefMember pref;
  pref.Init(pref_name, profile->GetPrefs());
  pref.SetValue(value);
}

void SetIntegerUserPref(ProfileIOS* profile, const char* pref_name, int value) {
  DCHECK(profile);
  DCHECK(profile->GetPrefs());
  IntegerPrefMember pref;
  pref.Init(pref_name, profile->GetPrefs());
  pref.SetValue(value);
}

void SetDoubleUserPref(ProfileIOS* profile,
                       const char* pref_name,
                       double value) {
  DCHECK(profile);
  DCHECK(profile->GetPrefs());
  DoublePrefMember pref;
  pref.Init(pref_name, profile->GetPrefs());
  pref.SetValue(value);
}

bool IsMetricsRecordingEnabled() {
  DCHECK(GetApplicationContext());
  DCHECK(GetApplicationContext()->GetMetricsService());
  return GetApplicationContext()->GetMetricsService()->recording_active();
}

bool IsMetricsReportingEnabled() {
  DCHECK(GetApplicationContext());
  DCHECK(GetApplicationContext()->GetMetricsService());
  return GetApplicationContext()->GetMetricsService()->reporting_active();
}

bool IsCrashpadEnabled() {
  return crash_reporter::IsCrashpadRunning();
}

bool IsCrashpadReportingEnabled() {
  return crash_helper::common::UserEnabledUploading();
}

bool PurgeCachedWebViewPages() {
  web::WebState* web_state = chrome_test_util::GetCurrentWebState();
  const GURL last_committed_url = web_state->GetLastCommittedURL();

  web_state->SetWebUsageEnabled(false);
  web_state->SetWebUsageEnabled(true);

  auto observer = std::make_unique<web::FakeWebStateObserver>(web_state);
  web::FakeWebStateObserver* observer_ptr = observer.get();

  web_state->GetNavigationManager()->LoadIfNecessary();

  // The navigation triggered by LoadIfNecessary() may only start loading in the
  // next run loop, if it is for a web URL. The most reliable way to detect that
  // this navigation has finished is via the WebStateObserver.
  return base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForPageLoadTimeout, ^{
        return observer_ptr->did_finish_navigation_info() &&
               observer_ptr->did_finish_navigation_info()->context &&
               observer_ptr->did_finish_navigation_info()
                       ->context->GetWebState()
                       ->GetVisibleURL() == last_committed_url;
      });
}

}  // namespace chrome_test_util
