"""
EZ-Audit Core Module
Provides the primary decorator and configuration logic for performance monitoring.
"""

import time
import tracemalloc
import logging
import functools
import platform
import sys
from typing import Optional, Callable, Any, TypeVar

# Type variable to help IDEs maintain function signatures
F = TypeVar('F', bound=Callable[..., Any])


class AuditConfig:
    """Holds the global configuration state for EZ-Audit."""

    def __init__(self) -> None:
        self.log_file: str = "audit.log"
        self.console_output: bool = True
        self._initialized: bool = False


_config = AuditConfig()


def format_size(bytes_size: float) -> str:
    """
    Converts bytes to a human-readable string (B, KB, MB, GB).

    Args:
        bytes_size: The number of bytes to format.
    Returns:
        A string with the size and appropriate unit.
    """
    for unit in ['B', 'KB', 'MB', 'GB']:
        if bytes_size < 1024:
            return f"{bytes_size:.2f}{unit}"
        bytes_size /= 1024
    return f"{bytes_size:.2f}GB"


def configure(log_file: Optional[str] = None, console_output: Optional[bool] = None) -> None:
    """
    Global configuration for EZ-Audit.

    Args:
        log_file: Path to the log file.
        console_output: Whether to print results to the console.
    """
    if log_file is not None:
        _config.log_file = log_file
    if console_output is not None:
        _config.console_output = console_output

    logger = logging.getLogger("ezaudit")
    logger.setLevel(logging.INFO)

    if logger.hasHandlers():
        for handler in logger.handlers[:]:
            handler.close()
            logger.removeHandler(handler)

    try:
        file_handler = logging.FileHandler(_config.log_file, encoding='utf-8')
        formatter = logging.Formatter(
            '%(asctime)s | %(levelname)s | %(message)s')
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)
    except (IOError, OSError) as exc:
        print(f"[EZ-AUDIT ERROR] Could not initialize log file: {exc}")

    if not _config._initialized:
        py_ver = sys.version.split()[0]
        header = f"--- SESSION START | OS: {platform.system()} | Python: {py_ver} ---"
        logger.info(header)
        _config._initialized = True


class audit_block:
    """
    Context manager to audit a specific block of code.
    Usage:
        with audit_block("Database Query"):
            do_query()
    """

    def __init__(self, name: str) -> None:
        self.name = name
        self.start_time: float = 0.0

    def __enter__(self) -> 'audit_block':
        if not logging.getLogger("ezaudit").hasHandlers():
            configure()
        tracemalloc.start()
        self.start_time = time.perf_counter()
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        duration = time.perf_counter() - self.start_time
        _, peak = tracemalloc.get_traced_memory()
        tracemalloc.stop()

        status = "SUCCESS" if exc_type is None else "FAILED"
        msg = (f"Block: {self.name} | Status: {status} | "
               f"Duration: {duration:.4f}s | Peak: {format_size(peak)}")

        logger = logging.getLogger("ezaudit")
        if _config.console_output:
            print(f"[EZ-AUDIT] {msg}")
        logger.info(msg)


def monitor(threshold_sec: Optional[float] = None) -> Any:
    """
    Decorator to monitor function performance.

    Args:
        threshold_sec: Optional time limit in seconds.
    """
    def decorator(func: F) -> F:
        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            if not logging.getLogger("ezaudit").hasHandlers():
                configure()

            tracemalloc.start()
            start_time = time.perf_counter()
            status = "SUCCESS"
            error_details = ""

            try:
                return func(*args, **kwargs)
            except Exception as exc:
                status = "FAILED"
                error_details = f" | Error: {type(exc).__name__}"
                raise exc
            finally:
                duration = time.perf_counter() - start_time
                _, peak = tracemalloc.get_traced_memory()
                tracemalloc.stop()

                raw_args = f"Args: {args} {kwargs}"
                safe_args = (
                    raw_args[:75] + '...') if len(raw_args) > 75 else raw_args

                audit_msg = (
                    f"Func: {func.__name__} | Status: {status} | "
                    f"Duration: {duration:.4f}s | Peak: {format_size(peak)} | "
                    f"{safe_args}{error_details}"
                )

                logger = logging.getLogger("ezaudit")
                is_over = threshold_sec and duration > threshold_sec

                if is_over:
                    final_msg = f"⚠️ [THRESHOLD EXCEEDED] {audit_msg}"
                    logger.warning(final_msg)
                else:
                    final_msg = f"[EZ-AUDIT] {audit_msg}"
                    logger.info(audit_msg)

                if _config.console_output:
                    print(final_msg)

        return wrapper  # type: ignore

    if callable(threshold_sec):
        func_to_wrap = threshold_sec
        threshold_sec = None
        return decorator(func_to_wrap)
    return decorator
