import time
import logging
from typing import Callable

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


class PerformanceOptimizer:
    """
    A class to optimize the performance of RWL (Read, Write, and Listen) operations.
    This includes reducing execution time and resource consumption.
    """

    def __init__(self, baseline_execution_time: float, target_execution_time: float):
        """
        Initializes the PerformanceOptimizer.

        Args:
            baseline_execution_time (float): The baseline execution time in seconds.
            target_execution_time (float): The target execution time in seconds.
        """
        self.baseline_execution_time = baseline_execution_time
        self.target_execution_time = target_execution_time
        self.optimization_strategies = []  # List to hold optimization functions

    def add_optimization_strategy(self, strategy: Callable):
        """
        Adds an optimization strategy to the list of strategies to be applied.

        Args:
            strategy (Callable): A function representing the optimization strategy.
                                  It should take no arguments and return None.
        """
        if not callable(strategy):
            raise ValueError("The provided strategy must be a callable function.")
        self.optimization_strategies.append(strategy)
        logging.info(f"Added optimization strategy: {strategy.__name__}")

    def apply_optimizations(self):
        """
        Applies the registered optimization strategies sequentially.
        Measures and logs the execution time after each optimization.
        """
        logging.info("Starting performance optimization...")
        start_time = time.time()

        for strategy in self.optimization_strategies:
            try:
                strategy()  # Execute the optimization strategy
                execution_time = time.time() - start_time
                logging.info(f"Applied optimization '{strategy.__name__}'. Execution time: {execution_time:.4f} seconds")
            except Exception as e:
                logging.error(f"Error applying optimization '{strategy.__name__}': {e}")

        final_execution_time = time.time() - start_time
        logging.info(f"Finished applying optimizations. Total execution time: {final_execution_time:.4f} seconds")

        if final_execution_time <= self.target_execution_time:
            logging.info("Performance optimization target achieved.")
        else:
            logging.warning(f"Performance optimization target not achieved.  Current execution time: {final_execution_time:.4f}s, Target: {self.target_execution_time:.4f}s")

    def monitor_performance(self, operation: Callable, num_iterations: int = 10):
        """
        Monitors the performance of a given operation by executing it multiple times
        and calculating the average execution time.

        Args:
            operation (Callable): The function representing the operation to monitor.
            num_iterations (int): The number of times to execute the operation.  Defaults to 10.

        Returns:
            float: The average execution time of the operation in seconds.
        """
        start_time = time.time()
        for _ in range(num_iterations):
            try:
                operation()
            except Exception as e:
                logging.error(f"Error during performance monitoring of '{operation.__name__}': {e}")
                return -1  # Indicate failure
        end_time = time.time()
        average_execution_time = (end_time - start_time) / num_iterations
        logging.info(f"Average execution time of '{operation.__name__}' over {num_iterations} iterations: {average_execution_time:.4f} seconds")
        return average_execution_time


def example_optimization_strategy():
    """
    Example optimization strategy (replace with actual optimizations).
    This function could, for example, optimize data fetching, reduce network calls, etc.
    """
    logging.info("Executing example optimization strategy...")
    time.sleep(0.1) # Simulate some optimization work


if __name__ == '__main__':
    # Example usage
    optimizer = PerformanceOptimizer(baseline_execution_time=6.0, target_execution_time=5.0)
    optimizer.add_optimization_strategy(example_optimization_strategy)
    optimizer.add_optimization_strategy(example_optimization_strategy)  # Add it twice to show effect
    optimizer.apply_optimizations()

    def example_operation():
        time.sleep(0.05)

    optimizer.monitor_performance(example_operation)