// Copyright 2021 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/app/safe_mode_app_state_agent.h"

#import "base/ios/block_types.h"
#import "base/test/ios/wait_util.h"
#import "ios/chrome/app/application_delegate/app_init_stage_test_utils.h"
#import "ios/chrome/app/application_delegate/app_state+Testing.h"
#import "ios/chrome/app/safe_mode_app_state_agent+private.h"
#import "ios/chrome/browser/safe_mode/ui_bundled/safe_mode_coordinator.h"
#import "ios/chrome/browser/shared/coordinator/scene/test/fake_scene_state.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/test/block_cleanup_test.h"
#import "ios/chrome/test/scoped_key_window.h"
#import "ios/testing/scoped_block_swizzler.h"
#import "ios/web/public/test/web_task_environment.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

namespace {
// A block that takes self as argument and return a BOOL.
typedef BOOL (^DecisionBlock)(id self);
}  // namespace

class SafeModeAppStateAgentTest : public BlockCleanupTest {
 protected:
  SafeModeAppStateAgentTest() {
    profile_ = TestProfileIOS::Builder().Build();
    window_ = [OCMockObject mockForClass:[UIWindow class]];

    AppState* app_state = [[AppState alloc] initWithStartupInformation:nil];
    app_state_mock_ = OCMPartialMock(app_state);

    main_scene_state_ = [[FakeSceneState alloc] initWithProfile:profile_.get()];
    main_scene_state_.window = window_;

    agent_ = [[SafeModeAppAgent alloc] init];
    [agent_ setAppState:app_state_mock_];
  }

  ~SafeModeAppStateAgentTest() override {
    [main_scene_state_ shutdown];
    main_scene_state_ = nil;
  }

  void SwizzleSafeModeShouldStart(BOOL shouldStart) {
    safe_mode_swizzle_block_ = ^BOOL(id self) {
      return shouldStart;
    };
    safe_mode_swizzler_.reset(new ScopedBlockSwizzler(
        [SafeModeCoordinator class], @selector(shouldStart),
        safe_mode_swizzle_block_));
  }

  // Iterate through the init stages from `curr_stage` up to
  // `dest_stage`.
  void IterateToStage(AppInitStage curr_stage, AppInitStage dest_stage) {
    DCHECK_GE(dest_stage, curr_stage);

    while (dest_stage != curr_stage) {
      const AppInitStage from_stage = curr_stage;
      curr_stage = NextAppInitStage(from_stage);
      [app_state_mock_ setInitStage:curr_stage];
      [agent_ appState:app_state_mock_ didTransitionFromInitStage:from_stage];
    }
  }

  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestProfileIOS> profile_;
  id app_state_mock_;
  FakeSceneState* main_scene_state_;
  SafeModeAppAgent* agent_;

  std::unique_ptr<ScopedBlockSwizzler> safe_mode_swizzler_;
  DecisionBlock safe_mode_swizzle_block_;

  id window_;
};

TEST_F(SafeModeAppStateAgentTest, startSafeMode) {
  [[window_ expect] makeKeyAndVisible];
  [[[window_ stub] andReturn:nil] rootViewController];
  [[window_ stub] setRootViewController:[OCMArg any]];

  SwizzleSafeModeShouldStart(YES);

  IterateToStage(AppInitStage::kStart, AppInitStage::kSafeMode);

  [agent_ sceneState:main_scene_state_
      transitionedToActivationLevel:SceneActivationLevelForegroundActive];
  // Second call that should be deduped.
  [agent_ sceneState:main_scene_state_
      transitionedToActivationLevel:SceneActivationLevelForegroundActive];

  EXPECT_OCMOCK_VERIFY(window_);

  // Exit safe mode.
  [agent_ coordinatorDidExitSafeMode:agent_.safeModeCoordinator];

  EXPECT_EQ(nil, agent_.safeModeCoordinator);
}

TEST_F(SafeModeAppStateAgentTest, dontStartSafeModeBecauseNotNeeded) {
  [[app_state_mock_ expect] queueTransitionToNextInitStage];

  SwizzleSafeModeShouldStart(NO);
  IterateToStage(AppInitStage::kStart, AppInitStage::kSafeMode);

  main_scene_state_.activationLevel = SceneActivationLevelForegroundActive;

  EXPECT_OCMOCK_VERIFY(app_state_mock_);
}

TEST_F(SafeModeAppStateAgentTest, dontStartSafeModeBecauseNotActiveLevel) {
  [[app_state_mock_ reject] queueTransitionToNextInitStage];

  SwizzleSafeModeShouldStart(YES);
  IterateToStage(AppInitStage::kStart, AppInitStage::kSafeMode);

  main_scene_state_.activationLevel = SceneActivationLevelForegroundInactive;

  EXPECT_OCMOCK_VERIFY(app_state_mock_);
}

TEST_F(SafeModeAppStateAgentTest,
       dontStartSafeModeBecauseFirstSceneHasAlreadyActivated) {
  [[app_state_mock_ reject] queueTransitionToNextInitStage];

  SwizzleSafeModeShouldStart(YES);
  IterateToStage(AppInitStage::kStart, AppInitStage::kSafeMode);

  agent_.firstSceneHasActivated = YES;
  main_scene_state_.activationLevel = SceneActivationLevelForegroundActive;

  EXPECT_OCMOCK_VERIFY(app_state_mock_);
}
