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

#include "net/disk_cache/sql/sql_async_task_manager.h"

#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/task_environment.h"
#include "net/disk_cache/sql/sql_async_task_token.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace disk_cache {

class SqlAsyncTaskManagerTest : public testing::Test {
 public:
  SqlAsyncTaskManagerTest() = default;

 protected:
  void RunOnAllTasksComplete(base::OnceClosure callback) {
    manager_.RunOnAllTasksCompleteForTest(std::move(callback));
  }

  base::test::TaskEnvironment task_environment_;
  SqlAsyncTaskManager manager_;
};

TEST_F(SqlAsyncTaskManagerTest, Basic) {
  std::unique_ptr<SqlAsyncTaskToken> token = manager_.StartTask();
  token.reset();

  manager_.RunUntilAllTasksCompleteForTest();
}

TEST_F(SqlAsyncTaskManagerTest, RunImmediatelyIfNoTasks) {
  manager_.RunUntilAllTasksCompleteForTest();
}

TEST_F(SqlAsyncTaskManagerTest, MultipleTasks) {
  std::unique_ptr<SqlAsyncTaskToken> token1 = manager_.StartTask();
  std::unique_ptr<SqlAsyncTaskToken> token2 = manager_.StartTask();
  std::unique_ptr<SqlAsyncTaskToken> token3 = manager_.StartTask();

  token2.reset();
  token1.reset();
  token3.reset();

  manager_.RunUntilAllTasksCompleteForTest();
}

TEST_F(SqlAsyncTaskManagerTest, StartTaskAfterCountZeroInSameTask) {
  // This test verifies that if StartTask is called in the same task
  // after pending_task_count_ becomes 0, it doesn't trigger the callback.
  base::RunLoop run_loop;
  bool completed = false;

  RunOnAllTasksComplete(base::BindLambdaForTesting([&]() {
    completed = true;
    run_loop.Quit();
  }));

  {
    std::unique_ptr<SqlAsyncTaskToken> token1 = manager_.StartTask();
    // pending_task_count_ becomes 0, posts CheckAndRunCallback.
    token1.reset();

    // StartTask in the same task.
    std::unique_ptr<SqlAsyncTaskToken> token2 = manager_.StartTask();

    // The posted CheckAndRunCallback should see pending_task_count_ is 1.
    base::RunLoop run_loop_quiescence;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, run_loop_quiescence.QuitClosure());
    run_loop_quiescence.Run();
    EXPECT_FALSE(completed);

    token2.reset();  // posts another CheckAndRunCallback.
  }

  run_loop.Run();
  EXPECT_TRUE(completed);
}

TEST_F(SqlAsyncTaskManagerTest, MultiStepQuiescence) {
  // This simulates a multi-step process where one task's completion
  // triggers the next one.
  base::RunLoop run_loop;
  int step = 0;

  RunOnAllTasksComplete(base::BindLambdaForTesting([&]() {
    EXPECT_EQ(3, step);
    run_loop.Quit();
  }));

  base::RepeatingClosure run_step;
  run_step = base::BindLambdaForTesting([&]() {
    if (step < 3) {
      std::unique_ptr<SqlAsyncTaskToken> token = manager_.StartTask();
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindLambdaForTesting([run_step, t = std::move(token),
                                                 &step]() mutable {
            step++;
            t.reset();  // count might hit 0 here.
            run_step
                .Run();  // if step < 3, count becomes 1 again in the same task.
          }));
    }
  });

  run_step.Run();

  run_loop.Run();
  EXPECT_EQ(3, step);
}

TEST_F(SqlAsyncTaskManagerTest, ManagerDestroyedFirst) {
  auto local_manager = std::make_unique<SqlAsyncTaskManager>();
  std::unique_ptr<SqlAsyncTaskToken> token = local_manager->StartTask();

  local_manager.reset();
  // Token destruction should not crash even if manager is gone.
  token.reset();
}

TEST_F(SqlAsyncTaskManagerTest, MultipleTasksStartedAndDestroyed) {
  std::unique_ptr<SqlAsyncTaskToken> token1 = manager_.StartTask();
  token1.reset();

  std::unique_ptr<SqlAsyncTaskToken> token2 = manager_.StartTask();
  token2.reset();

  manager_.RunUntilAllTasksCompleteForTest();
}

TEST_F(SqlAsyncTaskManagerTest, DestroyOnDifferentThreadDuringShutdown) {
  auto local_manager = std::make_unique<SqlAsyncTaskManager>();
  std::unique_ptr<SqlAsyncTaskToken> token = local_manager->StartTask();

  // Simulate shutdown by destroying the manager.
  local_manager.reset();

  // Destroy the token on a different thread. This should not crash.
  base::RunLoop run_loop;
  base::ThreadPool::PostTask(
      FROM_HERE,
      base::BindLambdaForTesting([t = std::move(token), &run_loop]() mutable {
        t.reset();
        run_loop.Quit();
      }));
  run_loop.Run();
}

TEST_F(SqlAsyncTaskManagerTest,
       DestroyOnDifferentThreadWithoutShutdownCrashes) {
  std::unique_ptr<SqlAsyncTaskToken> token = manager_.StartTask();

  // Destroy the token on a different thread while manager is still alive.
  // This should crash (trigger CHECK).
  EXPECT_CHECK_DEATH({
    base::RunLoop run_loop;
    base::ThreadPool::PostTask(
        FROM_HERE,
        base::BindLambdaForTesting([t = std::move(token), &run_loop]() mutable {
          t.reset();
          run_loop.Quit();
        }));
    run_loop.Run();
  });
}

}  // namespace disk_cache
