import time
import threading

class DynamicThrottler:
    """Dynamically adjusts the email sending rate based on system load and policies."""

    def __init__(self, initial_rate: int, policies: list, system_load_monitor):
        """
        Initializes the DynamicThrottler.

        Args:
            initial_rate: The initial email sending rate (emails per second).
            policies: A list of policies to evaluate against system load.
                     Each policy should be a dictionary with keys:
                         'threshold': The system load threshold.
                         'rate': The desired email sending rate when the threshold is met.
                         'operator':  '>', '<', '>=', '<='.  The operator to compare load to threshold.
            system_load_monitor: An object that provides system load metrics.
                                 It should have a method `get_load()` that returns the current system load.
        """
        self.rate = initial_rate
        self.policies = policies
        self.system_load_monitor = system_load_monitor
        self.lock = threading.Lock()
        self.running = True
        self.adjustment_interval = 1  # seconds - frequency of rate adjustment
        self.smooth_adjustment_factor = 0.1 # How smoothly the rate changes (0.1 means 10% adjustment)

        self.thread = threading.Thread(target=self._monitor_and_adjust)
        self.thread.daemon = True  # Allow the main program to exit even if this thread is running
        self.thread.start()

    def start(self):
        """Starts the throttling service."""
        self.running = True
        #In case the thread died for some reason, restart it.
        if not self.thread.is_alive():
            self.thread = threading.Thread(target=self._monitor_and_adjust)
            self.thread.daemon = True
            self.thread.start()

    def stop(self):
        """Stops the throttling service."""
        self.running = False
        self.thread.join(timeout=5) #Wait for the thread to finish, but not forever.

    def get_rate(self) -> int:
        """Returns the current email sending rate."""
        with self.lock:
            return int(self.rate)

    def _monitor_and_adjust(self):
        """Monitors system load and adjusts the email sending rate accordingly."""
        while self.running:
            try:
                load = self.system_load_monitor.get_load()
                new_rate = self._evaluate_policies(load)

                with self.lock:
                    # Smoothly adjust the rate
                    rate_diff = new_rate - self.rate
                    adjustment = rate_diff * self.smooth_adjustment_factor
                    self.rate += adjustment

                time.sleep(self.adjustment_interval)

            except Exception as e:
                #Log the exception, but keep the thread running.  Don't want to crash the service.
                print(f"Error in throttling service: {e}")
                time.sleep(self.adjustment_interval)


    def _evaluate_policies(self, load: float) -> float:
        """Evaluates the defined policies against the current system load.
        Returns the new rate based on the policies.
        If no policy is met, returns the current rate.
        """
        best_rate = self.rate #Default to the current rate.
        for policy in self.policies:
            threshold = policy['threshold']
            rate = policy['rate']
            operator = policy['operator']

            if operator == '>':
                if load > threshold:
                    best_rate = rate
            elif operator == '<':
                if load < threshold:
                    best_rate = rate
            elif operator == '>=':
                if load >= threshold:
                    best_rate = rate
            elif operator == '<=':
                if load <= threshold:
                    best_rate = rate
            else:
                print(f"Invalid operator in policy: {operator}") # Log invalid operator. Consider raising an exception

        return best_rate


if __name__ == '__main__':
    # Example usage (requires a SystemLoadMonitor implementation)
    class MockSystemLoadMonitor:
        def __init__(self, initial_load=0.5):
            self.load = initial_load

        def get_load(self):
            # Simulate fluctuating system load
            self.load += 0.05
            if self.load > 1.0:
                self.load = 0.1
            return self.load

    system_load_monitor = MockSystemLoadMonitor()

    policies = [
        {'threshold': 0.7, 'rate': 50, 'operator': '>='},
        {'threshold': 0.3, 'rate': 150, 'operator': '<='},
    ]

    throttler = DynamicThrottler(initial_rate=100, policies=policies, system_load_monitor=system_load_monitor)
    throttler.start()

    try:
        for i in range(10):
            print(f"Iteration {i+1}: Current rate: {throttler.get_rate()}, System Load: {system_load_monitor.get_load()}")
            time.sleep(1)
    except KeyboardInterrupt:
        print("Stopping throttler...")
    finally:
        throttler.stop()
        print("Throttler stopped.")