namespace media {

namespace {
int kCheckIntervalMs = 10;
int kTimeoutMs = 50;
}  // namespace

class MockPowerObserverHelper : public PowerObserverHelper {
 public:
  MockPowerObserverHelper(scoped_refptr<base::SequencedTaskRunner> task_runner,
                          base::RepeatingClosure suspend_callback,
                          base::RepeatingClosure resume_callback)

      : PowerObserverHelper(std::move(task_runner),
                            std::move(suspend_callback),
                            std::move(resume_callback)) {}

  bool IsSuspending() const override {
    DCHECK(TaskRunnerForTesting()->RunsTasksInCurrentSequence());
    return is_suspending_;
  }

  void Suspend() {
    DCHECK(TaskRunnerForTesting()->RunsTasksInCurrentSequence());
    is_suspending_ = true;
    SuspendCallbackForTesting()->Run();
  }

  void Resume() {
    DCHECK(TaskRunnerForTesting()->RunsTasksInCurrentSequence());
    is_suspending_ = false;
    ResumeCallbackForTesting()->Run();
  }

 private:
  bool is_suspending_ = false;
};

class AliveCheckerTest : public testing::Test {
 public:
  AliveCheckerTest()
      : alive_checker_thread_("AliveCheckerThread"),
        detected_dead_event_(base::WaitableEvent::ResetPolicy::MANUAL,
                             base::WaitableEvent::InitialState::NOT_SIGNALED) {
    alive_checker_thread_.StartAndWaitForTesting();
  }

  AliveCheckerTest(const AliveCheckerTest&) = delete;
  AliveCheckerTest& operator=(const AliveCheckerTest&) = delete;

  void OnDetectedDead() {
    EXPECT_TRUE(alive_checker_thread_.task_runner()->BelongsToCurrentThread());
    detected_dead_event_.Signal();
  }

  std::unique_ptr<PowerObserverHelper> CreatePowerObserverHelper(
      scoped_refptr<base::SequencedTaskRunner> task_runner,
      base::RepeatingClosure suspend_callback,
      base::RepeatingClosure resume_callback) {
    std::unique_ptr<MockPowerObserverHelper> mock_power_observer_helper =
        std::make_unique<MockPowerObserverHelper>(std::move(task_runner),
                                                  std::move(suspend_callback),
                                                  std::move(resume_callback));
    mock_power_observer_helper_ = mock_power_observer_helper.get();
    return mock_power_observer_helper;
  }

 protected:
  ~AliveCheckerTest() override {
    base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
                             base::WaitableEvent::InitialState::NOT_SIGNALED);
    alive_checker_thread_.task_runner()->PostTask(
        FROM_HERE,
        base::BindOnce(&AliveCheckerTest::ResetAliveCheckerOnAliveCheckerThread,
                       base::Unretained(this), &done));
    done.Wait();
  }

  void CreateAliveChecker(bool stop_at_first_alive_notification,
                          bool pause_check_during_suspend) {
    base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
                             base::WaitableEvent::InitialState::NOT_SIGNALED);
    alive_checker_thread_.task_runner()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &AliveCheckerTest::CreateAliveCheckerOnAliveCheckerThread,
            base::Unretained(this), stop_at_first_alive_notification,
            pause_check_during_suspend, &done));
    done.Wait();
  }

  void StartAliveChecker() {
    alive_checker_thread_.task_runner()->PostTask(
        FROM_HERE, base::BindOnce(&AliveChecker::Start,
                                  base::Unretained(alive_checker_.get())));
  }

  void StopAliveChecker() {
    alive_checker_thread_.task_runner()->PostTask(
        FROM_HERE, base::BindOnce(&AliveChecker::Stop,
                                  base::Unretained(alive_checker_.get())));
  }

  // Notifies |alive_checker_| that we're alive, and if
  // |remaining_notifications| > 1, posts a delayed task to itself on
  // |alive_checker_thread_| with |remaining_notifications| decreased by 1. Can
  // be called on any task runner.
  void NotifyAliveMultipleTimes(int remaining_notifications,
                                base::TimeDelta delay) {
    alive_checker_->NotifyAlive();
    if (remaining_notifications > 1) {
      alive_checker_thread_.task_runner()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&AliveCheckerTest::NotifyAliveMultipleTimes,
                         base::Unretained(this), remaining_notifications - 1,
                         delay),
          delay);
    }
  }

  void WaitUntilDetectedDead() {
    detected_dead_event_.Wait();
    detected_dead_event_.Reset();
  }

  // Returns true if the dead callback (AliveCheckerTest::OnDetectedDead) is run
  // by the AliveChecker, false if timed out.
  bool WaitUntilDetectedDeadWithTimeout(base::TimeDelta timeout) {
    bool signaled = detected_dead_event_.TimedWait(timeout);
    detected_dead_event_.Reset();
    return signaled;
  }

  // Calls AliveChecker::DetectedDead() on the |alive_checker_thread_| and
  // returns the result.
  bool GetDetectedDead() {
    bool detected_dead = false;
    base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
                             base::WaitableEvent::InitialState::NOT_SIGNALED);
    alive_checker_thread_.task_runner()->PostTask(
        FROM_HERE,
        base::BindOnce(&AliveCheckerTest::GetDetectedDeadOnAliveCheckerThread,
                       base::Unretained(this), &detected_dead, &done));
    done.Wait();
    return detected_dead;
  }

  // The test task environment.
  base::test::TaskEnvironment task_environment_;

  // The thread the checker is run on.
  base::Thread alive_checker_thread_;

  // AliveChecker under test.
  std::unique_ptr<AliveChecker> alive_checker_;

  // Mocks suspend status. Set in CreatePowerObserverHelper, owned by
  // |alive_checker_|.
  raw_ptr<MockPowerObserverHelper> mock_power_observer_helper_;

 private:
  void CreateAliveCheckerOnAliveCheckerThread(
      bool stop_at_first_alive_notification,
      bool pause_check_during_suspend,
      base::WaitableEvent* done) {
    EXPECT_TRUE(alive_checker_thread_.task_runner()->BelongsToCurrentThread());

    if (pause_check_during_suspend) {
      alive_checker_ = std::make_unique<AliveChecker>(
          base::BindRepeating(&AliveCheckerTest::OnDetectedDead,
                              base::Unretained(this)),
          base::Milliseconds(kCheckIntervalMs), base::Milliseconds(kTimeoutMs),
          stop_at_first_alive_notification,
          base::BindOnce(&AliveCheckerTest::CreatePowerObserverHelper,
                         base::Unretained(this)));
    } else {
      alive_checker_ = std::make_unique<AliveChecker>(
          base::BindRepeating(&AliveCheckerTest::OnDetectedDead,
                              base::Unretained(this)),
          base::Milliseconds(kCheckIntervalMs), base::Milliseconds(kTimeoutMs),
          stop_at_first_alive_notification, false);
    }

    done->Signal();
  }

  void GetDetectedDeadOnAliveCheckerThread(bool* detected_dead,
                                           base::WaitableEvent* done) {
    EXPECT_TRUE(alive_checker_thread_.task_runner()->BelongsToCurrentThread());
    *detected_dead = alive_checker_->DetectedDead();
    done->Signal();
  }

  void ResetAliveCheckerOnAliveCheckerThread(base::WaitableEvent* done) {
    EXPECT_TRUE(alive_checker_thread_.task_runner()->BelongsToCurrentThread());
    alive_checker_.reset();
    done->Signal();
  }

  // Event to signal that we got a dead detection callback.
  base::WaitableEvent detected_dead_event_;
};

// Start the checker, don't send alive notifications, and run until it detects
// dead. Verify that it only detects once. Repeat once.
TEST_F(AliveCheckerTest, NoAliveNotificationsDetectTwice) {
  CreateAliveChecker(false, false);

  StartAliveChecker();
  EXPECT_FALSE(GetDetectedDead());

  WaitUntilDetectedDead();
  EXPECT_TRUE(GetDetectedDead());

  // Verify that AliveChecker doesn't detect (runs the callback) a second time.
  // It can take up to the timeout + the check interval until detection. Add a
  // margin to this. The detect state should still be that we have detected
  // dead.
  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_TRUE(GetDetectedDead());

  // Start again, the detect state should be reset.
  StartAliveChecker();
  EXPECT_FALSE(GetDetectedDead());

  WaitUntilDetectedDead();
  EXPECT_TRUE(GetDetectedDead());
}

// Setup the checker to stop at first alive notification. Start it and notify
// that the client is alive once. Verify that we get no dead detection.
TEST_F(AliveCheckerTest, StopAtFirstAliveNotification_DoNotify) {
  CreateAliveChecker(true, false);

  StartAliveChecker();
  alive_checker_->NotifyAlive();

  // It can take up to the timeout + the check interval until detection. Add a
  // margin to this.
  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());
}

// Setup the checker to stop at first alive notification. Start it and run until
// it detects dead.
TEST_F(AliveCheckerTest, StopAtFirstAliveNotification_DontNotify) {
  CreateAliveChecker(true, false);
  StartAliveChecker();
  WaitUntilDetectedDead();
  EXPECT_TRUE(GetDetectedDead());
}

// Setup the checker to pause checking when suspended. Suspend and verify that
// it doesn't detect dead. Start the checker, don't send alive notifications,
// and and verify that it doesn't detect dead. Resume and run until it detects
// dead.
TEST_F(AliveCheckerTest, SuspendResume_StartBetweenSuspendAndResume) {
  CreateAliveChecker(false, true);
  ASSERT_TRUE(mock_power_observer_helper_);

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
                                base::Unretained(mock_power_observer_helper_)));

  StartAliveChecker();

  // It can take up to the timeout + the check interval until detection. Add a
  // margin to this.
  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
                                base::Unretained(mock_power_observer_helper_)));

  WaitUntilDetectedDead();
  EXPECT_TRUE(GetDetectedDead());
}

// Setup the checker to stop at first alive notification and pause checking when
// suspended. Start the checker, send one alive notifications, and verify it
// doesn't detect dead. Suspend and verify that it doesn't detect dead. Resume
// and and verify that it doesn't detect dead.
TEST_F(AliveCheckerTest, SuspendResumeWithAutoStop_NotifyBeforeSuspend) {
  CreateAliveChecker(true, true);
  ASSERT_TRUE(mock_power_observer_helper_);

  StartAliveChecker();
  alive_checker_->NotifyAlive();

  // It can take up to the timeout + the check interval until detection. Add a
  // margin to this.
  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
                                base::Unretained(mock_power_observer_helper_)));

  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
                                base::Unretained(mock_power_observer_helper_)));

  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());
}

// Setup the checker to stop at first alive notification and pause checking when
// suspended. Start the checker, send one alive notifications, and verify it
// doesn't detect dead. Start it again, suspend and verify that it doesn't
// detect dead. Resume and run until detected dead.
TEST_F(AliveCheckerTest,
       SuspendResumeWithAutoStop_NotifyBeforeSuspendAndRestart) {
  CreateAliveChecker(true, true);
  ASSERT_TRUE(mock_power_observer_helper_);

  StartAliveChecker();
  alive_checker_->NotifyAlive();

  // It can take up to the timeout + the check interval until detection. Add a
  // margin to this.
  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());

  StartAliveChecker();
  EXPECT_FALSE(GetDetectedDead());

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
                                base::Unretained(mock_power_observer_helper_)));

  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
                                base::Unretained(mock_power_observer_helper_)));

  WaitUntilDetectedDead();
  EXPECT_TRUE(GetDetectedDead());
}

// Setup the checker to stop at first alive notification and pause checking when
// suspended. Start the checker, suspend. Send one alive notification and
// verify it doesn't detected dead. Resume and verify it doesn't detected dead.
TEST_F(AliveCheckerTest,
       SuspendResumeWithAutoStop_NotifyBetweenSuspendAndResume) {
  CreateAliveChecker(true, true);
  ASSERT_TRUE(mock_power_observer_helper_);

  StartAliveChecker();

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
                                base::Unretained(mock_power_observer_helper_)));

  alive_checker_->NotifyAlive();

  // It can take up to the timeout + the check interval until detection. Add a
  // margin to this.
  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
                                base::Unretained(mock_power_observer_helper_)));

  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());
}

// Setup the checker to stop at first alive notification and pause checking when
// suspended. Start the checker, suspend, resume, send one alive notification
// and verify it doesn't detected dead.
TEST_F(AliveCheckerTest, SuspendResumeWithAutoStop_NotifyAfterResume) {
  CreateAliveChecker(true, true);
  ASSERT_TRUE(mock_power_observer_helper_);

  StartAliveChecker();

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
                                base::Unretained(mock_power_observer_helper_)));

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
                                base::Unretained(mock_power_observer_helper_)));

  alive_checker_->NotifyAlive();

  // It can take up to the timeout + the check interval until detection. Add a
  // margin to this.
  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());
}

// Setup the checker to stop at first alive notification and pause checking when
// suspended. Start the checker suspend, and and verify it doesn't detected
// dead. Resume and run until it detects dead.
TEST_F(AliveCheckerTest, SuspendResumeWithAutoStop_DontNotify) {
  CreateAliveChecker(true, true);
  ASSERT_TRUE(mock_power_observer_helper_);

  StartAliveChecker();

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
                                base::Unretained(mock_power_observer_helper_)));

  // It can take up to the timeout + the check interval until detection. Add a
  // margin to this.
  EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
      base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
  EXPECT_FALSE(GetDetectedDead());

  alive_checker_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
                                base::Unretained(mock_power_observer_helper_)));

  WaitUntilDetectedDead();
  EXPECT_TRUE(GetDetectedDead());
}

}  // namespace media
