/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <memory>

#include <fmt/core.h>

#include <folly/concurrency/DeadlockDetector.h>
#include <folly/executors/IOThreadPoolDeadlockDetectorObserver.h>
#include <folly/executors/IOThreadPoolExecutor.h>
#include <folly/portability/GTest.h>

using namespace folly;

class DeadlockDetectorMock : public DeadlockDetector {
 public:
  DeadlockDetectorMock(std::shared_ptr<std::atomic<int32_t>> counter)
      : counter_(counter) {
    counter_->fetch_add(1);
  }
  ~DeadlockDetectorMock() override { counter_->fetch_sub(1); }

 private:
  std::shared_ptr<std::atomic<int32_t>> counter_;
};

class DeadlockDetectorFactoryMock : public DeadlockDetectorFactory {
 public:
  std::unique_ptr<DeadlockDetector> create(
      folly::Executor* executor,
      const std::string& name,
      std::unique_ptr<WorkerProvider> tidCollector) override {
    EXPECT_TRUE(executor != nullptr);
    auto tid = folly::getOSThreadID();
    std::string expectedName = fmt::format("TestPool:{}", tid);
    EXPECT_EQ(expectedName, name);
    {
      auto tids = tidCollector->collectThreadIds();
      EXPECT_EQ(1, tids.threadIds.size());
      EXPECT_EQ(tid, tids.threadIds[0]);
    }
    name_ = name;
    auto retval = std::make_unique<DeadlockDetectorMock>(counter_);
    baton.post();
    return retval;
  }

  int32_t getCounter() { return counter_->load(); }

  std::string getName() const { return name_.copy(); }

  folly::Baton<> baton;

 private:
  std::shared_ptr<std::atomic<int32_t>> counter_ =
      std::make_shared<std::atomic<int32_t>>(0);
  folly::Synchronized<std::string> name_;
};

TEST(
    IOThreadPoolDeadlockDetectorObserverTest,
    VerifyDeadlockDetectorCreatedAndDestroyedOnAddRemoveObserver) {
  auto deadlockDetectorFactory =
      std::make_shared<DeadlockDetectorFactoryMock>();

  auto executor = std::make_shared<IOThreadPoolExecutor>(
      1, std::make_shared<folly::NamedThreadFactory>("TestPool"));
  pid_t thid;
  executor->getEventBase()->runInEventBaseThreadAndWait(
      [&] { thid = folly::getOSThreadID(); });

  auto observer = std::make_shared<folly::IOThreadPoolDeadlockDetectorObserver>(
      deadlockDetectorFactory.get(), "TestPool");

  ASSERT_EQ(0, deadlockDetectorFactory->getCounter())
      << "Deadlock detector not yet registered";

  executor->addObserver(observer);
  deadlockDetectorFactory->baton.wait();
  deadlockDetectorFactory->baton.reset();
  ASSERT_EQ(1, deadlockDetectorFactory->getCounter())
      << "Deadlock detector must be registered by observer";
  EXPECT_EQ(
      fmt::format("TestPool:{}", thid), deadlockDetectorFactory->getName());

  executor->removeObserver(observer);
  ASSERT_EQ(0, deadlockDetectorFactory->getCounter())
      << "Removing observer must destroy deadlock detector";

  executor->addObserver(observer);
  deadlockDetectorFactory->baton.wait();
  deadlockDetectorFactory->baton.reset();
  ASSERT_EQ(1, deadlockDetectorFactory->getCounter())
      << "Deadlock detector must be registered by observer";

  executor->stop();
  ASSERT_EQ(0, deadlockDetectorFactory->getCounter())
      << "Stopping threadpool must deregister all observers";
}
