"""
Utility functions for Gattopy.
"""

import time
import tracemalloc
from functools import wraps
from typing import Any, Callable, List, Optional


def format_bytes(bytes_value: float) -> str:
    """
    Format bytes to human-readable format.

    Args:
        bytes_value: Number of bytes

    Returns:
        Formatted string (e.g., "1.5 MB", "500 KB")
    """
    for unit in ["B", "KB", "MB", "GB", "TB"]:
        if abs(bytes_value) < 1024.0:
            return f"{bytes_value:.2f} {unit}"
        bytes_value /= 1024.0
    return f"{bytes_value:.2f} PB"


def format_time(seconds: float) -> str:
    """
    Format seconds to human-readable format.

    Args:
        seconds: Time in seconds

    Returns:
        Formatted string (e.g., "1.5 ms", "2.3 s")
    """
    if seconds < 1e-6:
        return f"{seconds * 1e9:.2f} ns"
    elif seconds < 1e-3:
        return f"{seconds * 1e6:.2f} μs"
    elif seconds < 1:
        return f"{seconds * 1e3:.2f} ms"
    elif seconds < 60:
        return f"{seconds:.2f} s"
    elif seconds < 3600:
        minutes = int(seconds // 60)
        secs = seconds % 60
        return f"{minutes}m {secs:.1f}s"
    else:
        hours = int(seconds // 3600)
        minutes = int((seconds % 3600) // 60)
        return f"{hours}h {minutes}m"


def benchmark(
    func: Callable,
    iterations: int = 1000,
    track_memory: bool = False,
    include_iterations: bool = False,
) -> dict:
    """
    Benchmark a function by running it multiple times.

    Args:
        func: Function to benchmark
        iterations: Number of iterations to run
        track_memory: Whether to track memory usage (default: False)
        include_iterations: Whether to include individual iteration data (default: False)

    Returns:
        Dictionary with benchmark results
    """
    times = []
    memory_peaks = [] if track_memory else None
    memory_currents = [] if track_memory else None
    iteration_data = [] if include_iterations else None

    # Start memory tracking if requested
    if track_memory:
        tracemalloc.start()

    for i in range(iterations):
        # Reset memory tracking for this iteration
        if track_memory:
            tracemalloc.clear_traces()

        start = time.perf_counter()
        func()
        end = time.perf_counter()

        iteration_time = end - start
        times.append(iteration_time)

        # Capture memory for this iteration
        if track_memory:
            current, peak = tracemalloc.get_traced_memory()
            peak_mb = peak / (1024 * 1024)
            current_mb = current / (1024 * 1024)
            memory_peaks.append(peak_mb)
            memory_currents.append(current_mb)

            if include_iterations:
                iteration_data.append(
                    {
                        "iteration": i + 1,
                        "time_seconds": iteration_time,
                        "memory_peak_mb": peak_mb,
                        "memory_current_mb": current_mb,
                    }
                )
        elif include_iterations:
            iteration_data.append({"iteration": i + 1, "time_seconds": iteration_time})

    if track_memory:
        tracemalloc.stop()

    times.sort()

    result = {
        "iterations": iterations,
        "total_time": sum(times),
        "average": sum(times) / len(times),
        "min": min(times),
        "max": max(times),
        "median": times[len(times) // 2],
        "p95": times[int(len(times) * 0.95)],
        "p99": times[int(len(times) * 0.99)],
    }

    # Add memory statistics if tracked
    if track_memory and memory_peaks:
        result["memory"] = {
            "average_peak_mb": sum(memory_peaks) / len(memory_peaks),
            "min_peak_mb": min(memory_peaks),
            "max_peak_mb": max(memory_peaks),
            "average_current_mb": sum(memory_currents) / len(memory_currents),
            "min_current_mb": min(memory_currents),
            "max_current_mb": max(memory_currents),
        }

    # Add iteration details if requested
    if include_iterations:
        result["iteration_details"] = iteration_data

    return result


def compare_functions(
    *functions: Callable,
    iterations: int = 100,
    track_memory: bool = False,
    include_iterations: bool = False,
) -> dict:
    """
    Compare the performance of multiple functions.

    Args:
        *functions: Functions to compare
        iterations: Number of iterations per function
        track_memory: Whether to track memory usage (default: False)
        include_iterations: Whether to include individual iteration data (default: False)

    Returns:
        Dictionary with comparison results
    """
    results = {}

    for func in functions:
        name = func.__name__
        results[name] = benchmark(func, iterations, track_memory, include_iterations)

    return results


def retry_on_error(max_attempts: int = 3, delay: float = 0.1):
    """
    Decorator to retry a function on error.

    Args:
        max_attempts: Maximum number of retry attempts
        delay: Delay between retries in seconds
    """

    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None

            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    if attempt < max_attempts - 1:
                        time.sleep(delay)

            raise last_exception

        return wrapper

    return decorator


def validate_input(data: Any, checks: List[Callable[[Any], bool]]) -> bool:
    """
    Validate input data against multiple checks.

    Args:
        data: Data to validate
        checks: List of validation functions

    Returns:
        True if all checks pass

    Raises:
        ValueError: If any check fails
    """
    if not data:
        raise ValueError("Invalid input data: data is None or empty")

    for i, check in enumerate(checks):
        if not check(data):
            raise ValueError(f"Validation check {i + 1} failed")

    return True


def aggregate_results(results: list) -> dict:
    """
    Aggregate multiple profiling results.

    Args:
        results: List of ProfileResult objects

    Returns:
        Dictionary with aggregated statistics
    """
    if not results:
        return {}

    times = [
        r.execution_time
        for r in results
        if hasattr(r, "execution_time") and r.execution_time
    ]
    memory_peaks = [
        r.memory_peak for r in results if hasattr(r, "memory_peak") and r.memory_peak
    ]

    aggregated = {}

    if times:
        aggregated["time"] = {
            "count": len(times),
            "total": sum(times),
            "average": sum(times) / len(times),
            "min": min(times),
            "max": max(times),
        }

    if memory_peaks:
        aggregated["memory"] = {
            "count": len(memory_peaks),
            "average_peak": sum(memory_peaks) / len(memory_peaks),
            "min_peak": min(memory_peaks),
            "max_peak": max(memory_peaks),
        }

    return aggregated
