import random
import time
import threading

class ABTestRateLimiter:
    def __init__(self, strategies, traffic_split):
        """Initializes the A/B testing rate limiter.

        Args:
            strategies (dict): A dictionary of rate limiting strategies, where the keys are strategy names
                                and the values are callable objects (e.g., classes or functions) that implement
                                rate limiting logic.  Each strategy should have an `allow_request` method.
            traffic_split (dict): A dictionary defining the traffic split for each strategy.  The keys are strategy
                                 names, and the values are the percentage of traffic (as a float between 0 and 1) that
                                 should be routed to that strategy. The sum of all values should be 1.0.
        """
        self.strategies = strategies
        self.traffic_split = traffic_split
        self.strategy_names = list(strategies.keys())
        self.strategy_weights = [traffic_split[name] for name in self.strategy_names]
        self.request_counts = {name: 0 for name in self.strategy_names}
        self.success_counts = {name: 0 for name in self.strategy_names}
        self.error_counts = {name: 0 for name in self.strategy_names}
        self.lock = threading.Lock()

    def choose_strategy(self):
        """Chooses a strategy based on the configured traffic split.

        Returns:
            str: The name of the chosen strategy.
        """
        return random.choices(self.strategy_names, self.strategy_weights)[0]

    def allow_request(self, request_context):
        """Checks if a request is allowed based on the chosen rate limiting strategy.

        Args:
            request_context (dict): A dictionary containing information about the request, such as the user ID,
                                    IP address, and requested resource.

        Returns:
            bool: True if the request is allowed, False otherwise.
        """
        strategy_name = self.choose_strategy()
        strategy = self.strategies[strategy_name]

        with self.lock:
            self.request_counts[strategy_name] += 1

        start_time = time.time()
        try:
            allowed = strategy.allow_request(request_context)
            if allowed:
                with self.lock:
                    self.success_counts[strategy_name] += 1
            return allowed
        except Exception as e:
            with self.lock:
                self.error_counts[strategy_name] += 1
            print(f"Error in strategy {strategy_name}: {e}")
            return False # Deny the request if there's an error in the strategy
        finally:
            end_time = time.time()
            # You could log latency here if needed
            pass

    def get_metrics(self):
        """Returns a dictionary of metrics for each rate limiting strategy.

        Returns:
            dict: A dictionary containing metrics for each strategy, including request counts, success counts, and error counts.
        """
        with self.lock:
            metrics = {
                name: {
                    "requests": self.request_counts[name],
                    "successes": self.success_counts[name],
                    "errors": self.error_counts[name]
                } for name in self.strategy_names
            }
        return metrics

    def switch_to_best_strategy(self, strategy_name):
        """Switches all traffic to the specified strategy.

        Args:
            strategy_name (str): The name of the strategy to switch to.
        """
        if strategy_name not in self.strategies:
            raise ValueError(f"Strategy '{strategy_name}' not found.")

        self.strategy_names = [strategy_name]
        self.strategy_weights = [1.0]
        self.traffic_split = {strategy_name: 1.0}
        print(f"Switched all traffic to strategy: {strategy_name}")


if __name__ == '__main__':
    # Example Usage (requires defining some dummy rate limiting strategies)
    class DummyRateLimiterA:
        def allow_request(self, request_context):
            # Simulate some rate limiting logic
            user_id = request_context.get('user_id')
            if user_id == 'test_user' and random.random() < 0.2:  # Simulate rate limiting for a specific user
                return False
            return True

    class DummyRateLimiterB:
        def allow_request(self, request_context):
            # Simulate different rate limiting logic
            ip_address = request_context.get('ip_address')
            if ip_address == '192.168.1.100' and random.random() < 0.5: # Simulate rate limiting for a specific IP
                return False
            return True

    strategies = {
        'strategy_a': DummyRateLimiterA(),
        'strategy_b': DummyRateLimiterB()
    }

    traffic_split = {
        'strategy_a': 0.5,
        'strategy_b': 0.5
    }

    ab_test_limiter = ABTestRateLimiter(strategies, traffic_split)

    # Simulate some requests
    for i in range(100):
        request_context = {
            'user_id': 'user_' + str(i % 10),
            'ip_address': '192.168.1.' + str(i + 100)
        }
        allowed = ab_test_limiter.allow_request(request_context)
        print(f"Request {i}: Allowed = {allowed}")
        time.sleep(0.01)

    # Get metrics
    metrics = ab_test_limiter.get_metrics()
    print("\nMetrics after simulation:", metrics)

    # Example: Switch to the best strategy (assuming strategy_a performed better)
    # In a real scenario, you'd analyze the metrics to determine the best strategy
    # ab_test_limiter.switch_to_best_strategy('strategy_a')
