import unittest
import time
import threading
import psutil

# Mock scaling service and metrics (replace with actual service in a real environment)
class MockScalingService:
    def __init__(self):
        self.scale_up_count = 0
        self.scale_down_count = 0
        self.scaling_events = []

    def scale_up(self, reason):
        self.scale_up_count += 1
        self.scaling_events.append({"action": "scale_up", "reason": reason})
        print(f"Scaled up. Reason: {reason}")

    def scale_down(self, reason):
        self.scale_down_count += 1
        self.scaling_events.append({"action": "scale_down", "reason": reason})
        print(f"Scaled down. Reason: {reason}")


class CPUMemoryPolicy:
    def __init__(self, scaling_service, cpu_threshold, memory_threshold, cpu_low_threshold, memory_low_threshold, evaluation_interval, cooldown_period):
        self.scaling_service = scaling_service
        self.cpu_threshold = cpu_threshold
        self.memory_threshold = memory_threshold
        self.cpu_low_threshold = cpu_low_threshold
        self.memory_low_threshold = memory_low_threshold
        self.evaluation_interval = evaluation_interval
        self.cooldown_period = cooldown_period
        self.last_scaled = 0
        self.high_cpu_count = 0
        self.high_memory_count = 0
        self.low_cpu_count = 0
        self.low_memory_count = 0

    def check_scaling(self):
        cpu_usage = psutil.cpu_percent(interval=1)
        memory_usage = psutil.virtual_memory().percent
        current_time = time.time()

        print(f"CPU Usage: {cpu_usage}%, Memory Usage: {memory_usage}%")

        if cpu_usage > self.cpu_threshold and memory_usage > self.memory_threshold:
            self.high_cpu_count += 1
            self.high_memory_count += 1
            self.low_cpu_count = 0
            self.low_memory_count = 0
            if self.high_cpu_count * self.evaluation_interval >= self.cooldown_period and current_time - self.last_scaled > self.cooldown_period:
                self.scaling_service.scale_up(f"CPU usage {cpu_usage}% and Memory usage {memory_usage}% exceeded thresholds")
                self.last_scaled = current_time
                self.high_cpu_count = 0
                self.high_memory_count = 0
        elif cpu_usage < self.cpu_low_threshold and memory_usage < self.memory_low_threshold:
            self.low_cpu_count += 1
            self.low_memory_count += 1
            self.high_cpu_count = 0
            self.high_memory_count = 0
            if self.low_cpu_count * self.evaluation_interval >= self.cooldown_period and current_time - self.last_scaled > self.cooldown_period:
                self.scaling_service.scale_down(f"CPU usage {cpu_usage}% and Memory usage {memory_usage}% fell below thresholds")
                self.last_scaled = current_time
                self.low_cpu_count = 0
                self.low_memory_count = 0
        else:
            self.high_cpu_count = 0
            self.high_memory_count = 0
            self.low_cpu_count = 0
            self.low_memory_count = 0


class TestCPUMemoryScaling(unittest.TestCase):

    def test_scale_up_and_down(self):
        scaling_service = MockScalingService()
        cpu_threshold = 10  # Trigger scaling with relatively low CPU usage
        memory_threshold = 10 # Trigger scaling with relatively low memory usage
        cpu_low_threshold = 2
        memory_low_threshold = 2
        evaluation_interval = 1  # Check every 1 second
        cooldown_period = 3  # Wait 3 seconds between scaling actions

        policy = CPUMemoryPolicy(scaling_service, cpu_threshold, memory_threshold, cpu_low_threshold, memory_low_threshold, evaluation_interval, cooldown_period)

        # Simulate high CPU and Memory usage
        def simulate_high_usage():
            start_time = time.time()
            while time.time() - start_time < 5:
                # Allocate memory and perform CPU-intensive calculations
                data = [i**2 for i in range(1000000)]
                time.sleep(0.1)

        high_usage_thread = threading.Thread(target=simulate_high_usage)
        high_usage_thread.start()

        # Let high usage run for a bit
        time.sleep(2)

        # Check scaling multiple times
        for _ in range(5):
            policy.check_scaling()
            time.sleep(evaluation_interval)

        high_usage_thread.join()

        # Simulate low CPU and memory usage
        time.sleep(cooldown_period + 1)
        for _ in range(5):
            policy.check_scaling()
            time.sleep(evaluation_interval)

        # Assertions
        self.assertGreaterEqual(scaling_service.scale_up_count, 0, "Expected at least one scale-up event")
        self.assertGreaterEqual(scaling_service.scale_down_count, 0, "Expected at least one scale-down event")
        self.assertTrue(any(event['action'] == 'scale_up' for event in scaling_service.scaling_events), "Scale up event not found")
        self.assertTrue(any(event['action'] == 'scale_down' for event in scaling_service.scaling_events), "Scale down event not found")

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