/**
 * @file test_semaphore.cpp
 * @brief Comprehensive unit tests for ipc::sync::semaphore class
 * 
 * This test suite covers:
 * - Semaphore construction (default and named with count)
 * - Wait and post operations
 * - Timed wait with timeout
 * - Named semaphore for inter-process synchronization
 * - Resource cleanup (clear, clear_storage)
 * - Producer-consumer patterns
 * - Multiple wait/post scenarios
 */

#include <gtest/gtest.h>
#include <thread>
#include <chrono>
#include <atomic>
#include <vector>
#include "libipc/semaphore.h"
#include "libipc/def.h"

using namespace ipc;
using namespace ipc::sync;

namespace {

std::string generate_unique_sem_name(const char* prefix) {
  static int counter = 0;
  return std::string(prefix) + "_sem_" + std::to_string(++counter);
}

} // anonymous namespace

class SemaphoreTest : public ::testing::Test {
protected:
  void TearDown() override {
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
  }
};

// Test default constructor
TEST_F(SemaphoreTest, DefaultConstructor) {
  semaphore sem;
  // Default constructed semaphore
}

// Test named constructor with count
TEST_F(SemaphoreTest, NamedConstructorWithCount) {
  std::string name = generate_unique_sem_name("named_count");
  
  semaphore sem(name.c_str(), 5);
  EXPECT_TRUE(sem.valid());
}

// Test named constructor with zero count
TEST_F(SemaphoreTest, NamedConstructorZeroCount) {
  std::string name = generate_unique_sem_name("zero_count");
  
  semaphore sem(name.c_str(), 0);
  EXPECT_TRUE(sem.valid());
}

// Test native() methods
TEST_F(SemaphoreTest, NativeHandle) {
  std::string name = generate_unique_sem_name("native");
  
  semaphore sem(name.c_str(), 1);
  ASSERT_TRUE(sem.valid());
  
  const void* const_handle = static_cast<const semaphore&>(sem).native();
  void* handle = sem.native();
  
  EXPECT_NE(const_handle, nullptr);
  EXPECT_NE(handle, nullptr);
}

// Test valid() method
TEST_F(SemaphoreTest, Valid) {
  semaphore sem1;
  
  std::string name = generate_unique_sem_name("valid");
  semaphore sem2(name.c_str(), 1);
  EXPECT_TRUE(sem2.valid());
}

// Test open() method
TEST_F(SemaphoreTest, Open) {
  std::string name = generate_unique_sem_name("open");
  
  semaphore sem;
  bool result = sem.open(name.c_str(), 3);
  
  EXPECT_TRUE(result);
  EXPECT_TRUE(sem.valid());
}

// Test close() method
TEST_F(SemaphoreTest, Close) {
  std::string name = generate_unique_sem_name("close");
  
  semaphore sem(name.c_str(), 1);
  ASSERT_TRUE(sem.valid());
  
  sem.close();
  EXPECT_FALSE(sem.valid());
}

// Test clear() method
TEST_F(SemaphoreTest, Clear) {
  std::string name = generate_unique_sem_name("clear");
  
  semaphore sem(name.c_str(), 1);
  ASSERT_TRUE(sem.valid());
  
  sem.clear();
  EXPECT_FALSE(sem.valid());
}

// Test clear_storage() static method
TEST_F(SemaphoreTest, ClearStorage) {
  std::string name = generate_unique_sem_name("clear_storage");
  
  {
      semaphore sem(name.c_str(), 1);
      EXPECT_TRUE(sem.valid());
  }
  
  semaphore::clear_storage(name.c_str());
}

// Test basic wait and post
TEST_F(SemaphoreTest, WaitPost) {
  std::string name = generate_unique_sem_name("wait_post");
  
  semaphore sem(name.c_str(), 1);
  ASSERT_TRUE(sem.valid());
  
  bool waited = sem.wait();
  EXPECT_TRUE(waited);
  
  bool posted = sem.post();
  EXPECT_TRUE(posted);
}

// Test post with count
TEST_F(SemaphoreTest, PostWithCount) {
  std::string name = generate_unique_sem_name("post_count");
  
  semaphore sem(name.c_str(), 0);
  ASSERT_TRUE(sem.valid());
  
  bool posted = sem.post(5);
  EXPECT_TRUE(posted);
  
  // Now should be able to wait 5 times
  for (int i = 0; i < 5; ++i) {
      EXPECT_TRUE(sem.wait(10)); // 10ms timeout
  }
}

// Test timed wait with timeout
TEST_F(SemaphoreTest, TimedWait) {
  std::string name = generate_unique_sem_name("timed_wait");
  
  semaphore sem(name.c_str(), 1);
  ASSERT_TRUE(sem.valid());
  
  bool waited = sem.wait(100); // 100ms timeout
  EXPECT_TRUE(waited);
}

// Test wait timeout scenario
TEST_F(SemaphoreTest, WaitTimeout) {
  std::string name = generate_unique_sem_name("wait_timeout");
  
  semaphore sem(name.c_str(), 0); // Zero count
  ASSERT_TRUE(sem.valid());
  
  auto start = std::chrono::steady_clock::now();
  bool waited = sem.wait(50); // 50ms timeout
  auto end = std::chrono::steady_clock::now();
  
  auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
  
  // Should timeout
  EXPECT_FALSE(waited);
  EXPECT_GE(elapsed, 40); // Allow some tolerance
}

// Test infinite wait
TEST_F(SemaphoreTest, InfiniteWait) {
  std::string name = generate_unique_sem_name("infinite_wait");
  
  semaphore sem(name.c_str(), 0);
  ASSERT_TRUE(sem.valid());
  
  std::atomic<bool> wait_started{false};
  std::atomic<bool> wait_succeeded{false};
  
  std::thread waiter([&]() {
      wait_started.store(true);
      bool result = sem.wait(invalid_value);
      wait_succeeded.store(result);
  });
  
  // Wait for thread to start waiting
  while (!wait_started.load()) {
      std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }
  
  std::this_thread::sleep_for(std::chrono::milliseconds(50));
  
  // Post to release the waiter
  sem.post();
  
  waiter.join();
  
  EXPECT_TRUE(wait_succeeded.load());
}

// Test producer-consumer pattern
TEST_F(SemaphoreTest, ProducerConsumer) {
  std::string name = generate_unique_sem_name("prod_cons");
  
  semaphore sem(name.c_str(), 0);
  ASSERT_TRUE(sem.valid());
  
  std::atomic<int> produced{0};
  std::atomic<int> consumed{0};
  const int count = 10;
  
  std::thread producer([&]() {
      for (int i = 0; i < count; ++i) {
          ++produced;
          sem.post();
          std::this_thread::sleep_for(std::chrono::milliseconds(1));
      }
  });
  
  std::thread consumer([&]() {
      for (int i = 0; i < count; ++i) {
          sem.wait();
          ++consumed;
      }
  });
  
  producer.join();
  consumer.join();
  
  EXPECT_EQ(produced.load(), count);
  EXPECT_EQ(consumed.load(), count);
}

// Test multiple producers and consumers
TEST_F(SemaphoreTest, MultipleProducersConsumers) {
  std::string name = generate_unique_sem_name("multi_prod_cons");
  
  semaphore sem(name.c_str(), 0);
  ASSERT_TRUE(sem.valid());
  
  std::atomic<int> total_produced{0};
  std::atomic<int> total_consumed{0};
  const int items_per_producer = 5;
  const int num_producers = 3;
  const int num_consumers = 3;
  
  std::vector<std::thread> producers;
  for (int i = 0; i < num_producers; ++i) {
      producers.emplace_back([&]() {
          for (int j = 0; j < items_per_producer; ++j) {
              ++total_produced;
              sem.post();
              std::this_thread::yield();
          }
      });
  }
  
  std::vector<std::thread> consumers;
  for (int i = 0; i < num_consumers; ++i) {
      consumers.emplace_back([&]() {
          for (int j = 0; j < items_per_producer; ++j) {
              if (sem.wait(1000)) {
                  ++total_consumed;
              }
          }
      });
  }
  
  for (auto& t : producers) t.join();
  for (auto& t : consumers) t.join();
  
  EXPECT_EQ(total_produced.load(), items_per_producer * num_producers);
  EXPECT_EQ(total_consumed.load(), items_per_producer * num_producers);
}

// Test semaphore with initial count
TEST_F(SemaphoreTest, InitialCount) {
  std::string name = generate_unique_sem_name("initial_count");
  const uint32_t initial = 3;
  
  semaphore sem(name.c_str(), initial);
  ASSERT_TRUE(sem.valid());
  
  // Should be able to wait 'initial' times without blocking
  for (uint32_t i = 0; i < initial; ++i) {
      EXPECT_TRUE(sem.wait(10));
  }
  
  // Next wait should timeout
  EXPECT_FALSE(sem.wait(10));
}

// Test rapid post operations
TEST_F(SemaphoreTest, RapidPost) {
  std::string name = generate_unique_sem_name("rapid_post");
  
  semaphore sem(name.c_str(), 0);
  ASSERT_TRUE(sem.valid());
  
  const int post_count = 100;
  for (int i = 0; i < post_count; ++i) {
      EXPECT_TRUE(sem.post());
  }
  
  // Should be able to wait post_count times
  int wait_count = 0;
  for (int i = 0; i < post_count; ++i) {
      if (sem.wait(10)) {
          ++wait_count;
      }
  }
  
  EXPECT_EQ(wait_count, post_count);
}

// Test concurrent post operations
TEST_F(SemaphoreTest, ConcurrentPost) {
  std::string name = generate_unique_sem_name("concurrent_post");
  
  semaphore sem(name.c_str(), 0);
  ASSERT_TRUE(sem.valid());
  
  std::atomic<int> post_count{0};
  const int threads = 5;
  const int posts_per_thread = 10;
  
  std::vector<std::thread> posters;
  for (int i = 0; i < threads; ++i) {
      posters.emplace_back([&]() {
          for (int j = 0; j < posts_per_thread; ++j) {
              if (sem.post()) {
                  ++post_count;
              }
          }
      });
  }
  
  for (auto& t : posters) t.join();
  
  EXPECT_EQ(post_count.load(), threads * posts_per_thread);
  
  // Verify by consuming
  int consumed = 0;
  for (int i = 0; i < threads * posts_per_thread; ++i) {
      if (sem.wait(10)) {
          ++consumed;
      }
  }
  
  EXPECT_EQ(consumed, threads * posts_per_thread);
}

// Test reopen after close
TEST_F(SemaphoreTest, ReopenAfterClose) {
  std::string name = generate_unique_sem_name("reopen");
  
  semaphore sem;
  
  ASSERT_TRUE(sem.open(name.c_str(), 2));
  EXPECT_TRUE(sem.valid());
  
  sem.close();
  EXPECT_FALSE(sem.valid());
  
  ASSERT_TRUE(sem.open(name.c_str(), 3));
  EXPECT_TRUE(sem.valid());
}

// Test named semaphore sharing between threads
TEST_F(SemaphoreTest, NamedSemaphoreSharing) {
  std::string name = generate_unique_sem_name("sharing");
  
  std::atomic<int> value{0};
  
  std::thread t1([&]() {
      semaphore sem(name.c_str(), 0);
      ASSERT_TRUE(sem.valid());
      
      sem.wait(); // Wait for signal
      value.store(100);
  });
  
  std::thread t2([&]() {
      std::this_thread::sleep_for(std::chrono::milliseconds(50));
      
      semaphore sem(name.c_str(), 0);
      ASSERT_TRUE(sem.valid());
      
      sem.post(); // Signal t1
  });
  
  t1.join();
  t2.join();
  
  EXPECT_EQ(value.load(), 100);
}

// Test post multiple count at once
TEST_F(SemaphoreTest, PostMultiple) {
  std::string name = generate_unique_sem_name("post_multiple");
  
  semaphore sem(name.c_str(), 0);
  ASSERT_TRUE(sem.valid());
  
  const uint32_t count = 10;
  bool posted = sem.post(count);
  EXPECT_TRUE(posted);
  
  // Consume all
  for (uint32_t i = 0; i < count; ++i) {
      EXPECT_TRUE(sem.wait(10));
  }
  
  // Should be empty now
  EXPECT_FALSE(sem.wait(10));
}

// Test semaphore after clear
TEST_F(SemaphoreTest, AfterClear) {
  std::string name = generate_unique_sem_name("after_clear");
  
  semaphore sem(name.c_str(), 5);
  ASSERT_TRUE(sem.valid());
  
  sem.wait();
  sem.clear();
  EXPECT_FALSE(sem.valid());
  
  // Operations after clear should fail gracefully
  EXPECT_FALSE(sem.wait(10));
  EXPECT_FALSE(sem.post());
}

// Test zero timeout
TEST_F(SemaphoreTest, ZeroTimeout) {
  std::string name = generate_unique_sem_name("zero_timeout");
  
  semaphore sem(name.c_str(), 0);
  ASSERT_TRUE(sem.valid());
  
  bool waited = sem.wait(0);
  // Should return immediately (either success or timeout)
}

// Test high-frequency wait/post
TEST_F(SemaphoreTest, HighFrequency) {
  std::string name = generate_unique_sem_name("high_freq");
  
  semaphore sem(name.c_str(), 0);
  ASSERT_TRUE(sem.valid());
  
  std::thread poster([&]() {
      for (int i = 0; i < 1000; ++i) {
          sem.post();
      }
  });
  
  std::thread waiter([&]() {
      for (int i = 0; i < 1000; ++i) {
          sem.wait(100);
      }
  });
  
  poster.join();
  waiter.join();
}
