import threading
import time
import unittest
import random

class SharedResource:
    """Simulates a shared resource vulnerable to race conditions."""

    def __init__(self, initial_value=0):
        self.value = initial_value
        self.lock = threading.Lock()  # Protect access to the shared resource

    def increment(self, amount=1):
        """Increments the shared value in a non-atomic way."""
        self.value += amount

    def safe_increment(self, amount=1):
        """Increments the shared value using a lock to ensure atomicity."""
        with self.lock:
            self.value += amount

    def get_value(self):
        return self.value

class RaceConditionTests(unittest.TestCase):
    """Tests for race conditions in a concurrent environment."""

    def setUp(self):
        self.shared_resource = SharedResource()
        self.num_threads = 10
        self.increment_amount = 1000

    def increment_unsafe(self, resource, num_increments):
        """Increments the shared resource without a lock."""
        for _ in range(num_increments):
            resource.increment()

    def increment_safe(self, resource, num_increments):
        """Increments the shared resource with a lock."""
        for _ in range(num_increments):
            resource.safe_increment()

    def test_race_condition_unsafe(self):
        """Tests for a race condition when incrementing without a lock."""
        threads = []
        for _ in range(self.num_threads):
            thread = threading.Thread(target=self.increment_unsafe, args=(self.shared_resource, self.increment_amount))
            threads.append(thread)
            thread.start()

        for thread in threads:
            thread.join()

        expected_value = self.num_threads * self.increment_amount
        actual_value = self.shared_resource.get_value()
        print(f"Unsafe Increment: Expected {expected_value}, Actual {actual_value}")  # For race_report.md
        self.assertNotEqual(actual_value, expected_value, "Race condition not detected. Consider increasing num_threads or increment_amount.")

    def test_no_race_condition_safe(self):
        """Tests for a race condition when incrementing with a lock."""
        threads = []
        for _ in range(self.num_threads):
            thread = threading.Thread(target=self.increment_safe, args=(self.shared_resource, self.increment_amount))
            threads.append(thread)
            thread.start()

        for thread in threads:
            thread.join()

        expected_value = self.num_threads * self.increment_amount
        actual_value = self.shared_resource.get_value()
        print(f"Safe Increment: Expected {expected_value}, Actual {actual_value}") # For race_report.md
        self.assertEqual(actual_value, expected_value, "Race condition detected when using a lock!")

    def test_deadlock_potential(self):
      """Simulates a potential deadlock scenario using multiple locks."""
      lock_a = threading.Lock()
      lock_b = threading.Lock()

      def thread_a():
          with lock_a:
              time.sleep(0.1)  # Simulate some work
              with lock_b:
                  print("Thread A acquired both locks")

      def thread_b():
          with lock_b:
              time.sleep(0.1)  # Simulate some work
              with lock_a:
                  print("Thread B acquired both locks")

      thread1 = threading.Thread(target=thread_a)
      thread2 = threading.Thread(target=thread_b)

      thread1.start()
      thread2.start()

      thread1.join(timeout=1)  # Timeout to detect potential deadlock
      thread2.join(timeout=1)

      # If threads are still alive, assume a deadlock occurred (simplification)
      if thread1.is_alive() or thread2.is_alive():
          print("Potential Deadlock Detected!") # For race_report.md
          self.assertTrue(True, "Potential Deadlock Detected!")
      else:
          print("No Deadlock Detected") # For race_report.md
          self.assertFalse(False, "No Deadlock") # Force pass


if __name__ == '__main__':
    unittest.main()