import logging
import sys
from enum import StrEnum
from pathlib import Path
from datetime import datetime
from typing import Any


class LogLevel(StrEnum):
    """Log level enumeration with associated prefixes and ANSI colors."""
    DEBUG = "DEBUG"
    INFO = "INFO"
    WARNING = "WARNING"
    ERROR = "ERROR"
    CRITICAL = "CRITICAL"

    @property
    def prefix(self) -> str:
        return {
            LogLevel.DEBUG: "DEBUG",
            LogLevel.INFO: "INFO",
            LogLevel.WARNING: "WARN",
            LogLevel.ERROR: "ERROR",
            LogLevel.CRITICAL: "FATAL",
        }[self]

    @property
    def color(self) -> str:
        # ANSI colors; disabled if stdout is not a TTY
        if not sys.stdout.isatty():
            return ""
        return {
            LogLevel.DEBUG: "\033[36m",     # Cyan
            LogLevel.INFO: "\033[32m",      # Green
            LogLevel.WARNING: "\033[33m",   # Yellow
            LogLevel.ERROR: "\033[31m",     # Red
            LogLevel.CRITICAL: "\033[35m",  # Magenta
        }[self]

    @staticmethod
    def reset_color() -> str:
        return "" if not sys.stdout.isatty() else "\033[0m"


class VNALogger:
    """
    Enhanced logger for VNA system with consistent formatting and optional colors.

    Features
    --------
    - Consistent color coding across all modules (TTY-aware).
    - Component name namespacing (logger name: `vna.<component>`).
    - Optional file logging per component.
    - Lightweight performance timers.
    - Structured metadata via keyword arguments.
    """

    __slots__ = ("component_name", "log_file", "_logger", "_timers")
    _loggers: dict[str, "VNALogger"] = {}
    _base_config_set = False

    def __init__(self, component_name: str, log_file: Path | None = None) -> None:
        self.component_name = component_name
        self.log_file = log_file
        self._logger = logging.getLogger(f"vna.{component_name}")
        self._logger.setLevel(logging.DEBUG)
        self._logger.propagate = True  # use root handlers configured once
        self._timers: dict[str, float] = {}

        if not VNALogger._base_config_set:
            self._configure_base_logging()
            VNALogger._base_config_set = True

        if self.log_file:
            self._add_file_handler(self.log_file)

    # ---------------------------------------------------------------------
    # Base configuration (root logger + console format)
    # ---------------------------------------------------------------------
    def _configure_base_logging(self) -> None:
        root = logging.getLogger()
        root.setLevel(logging.DEBUG)

        # Remove existing handlers to avoid duplicates on reloads
        for h in root.handlers[:]:
            root.removeHandler(h)

        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setLevel(logging.DEBUG)
        console_handler.setFormatter(self._create_console_formatter())
        root.addHandler(console_handler)

    def _create_console_formatter(self) -> logging.Formatter:
        class ColoredFormatter(logging.Formatter):
            def format(self, record: logging.LogRecord) -> str:
                level = LogLevel(record.levelname) if record.levelname in LogLevel.__members__ else LogLevel.INFO
                timestamp = datetime.fromtimestamp(record.created).strftime("%H:%M:%S.%f")[:-3]
                component = f"[{record.name.replace('vna.', '')}]"
                color = level.color
                reset = LogLevel.reset_color()
                # Use record.getMessage() to apply %-formatting already handled by logging
                return f"{color}{level.prefix} {timestamp} {component:<20} {record.getMessage()}{reset}"

        return ColoredFormatter()

    # ---------------------------------------------------------------------
    # File handler
    # ---------------------------------------------------------------------
    def _add_file_handler(self, path: Path) -> None:
        path.parent.mkdir(parents=True, exist_ok=True)

        # Avoid adding duplicate file handlers for the same path
        for h in self._logger.handlers:
            if isinstance(h, logging.FileHandler) and Path(h.baseFilename) == path:
                return

        file_handler = logging.FileHandler(path, encoding="utf-8")
        file_handler.setLevel(logging.DEBUG)
        file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
        self._logger.addHandler(file_handler)

    # ---------------------------------------------------------------------
    # Public logging API
    # ---------------------------------------------------------------------
    def debug(self, message: str, /, **metadata: Any) -> None:
        self._log_with_metadata(logging.DEBUG, message, metadata)

    def info(self, message: str, /, **metadata: Any) -> None:
        self._log_with_metadata(logging.INFO, message, metadata)

    def warning(self, message: str, /, **metadata: Any) -> None:
        self._log_with_metadata(logging.WARNING, message, metadata)

    def error(self, message: str, /, **metadata: Any) -> None:
        self._log_with_metadata(logging.ERROR, message, metadata)

    def critical(self, message: str, /, **metadata: Any) -> None:
        self._log_with_metadata(logging.CRITICAL, message, metadata)

    def _log_with_metadata(self, level: int, message: str, metadata: dict[str, Any]) -> None:
        if metadata:
            # Render key=value; repr() helps keep types unambiguous in logs
            meta_str = " ".join(f"{k}={repr(v)}" for k, v in metadata.items())
            self._logger.log(level, f"{message} | {meta_str}")
        else:
            self._logger.log(level, message)

    # ---------------------------------------------------------------------
    # Timers
    # ---------------------------------------------------------------------
    def timer_start(self, operation: str) -> str:
        """
        Start a high-resolution timer for performance measurement.

        Returns
        -------
        str
            Timer identifier to be passed to `timer_end`.
        """
        # perf_counter() is monotonic & high-resolution
        timer_id = f"{self.component_name}:{operation}:{datetime.now().timestamp()}"
        self._timers[timer_id] = datetime.now().timestamp()
        self.debug("Timer started", operation=operation, timer_id=timer_id)
        return timer_id

    def timer_end(self, timer_id: str, operation: str | None = None) -> float:
        """
        End a timer and log the elapsed time.

        Returns
        -------
        float
            Elapsed time in milliseconds. Returns 0.0 if timer_id is unknown.
        """
        started = self._timers.pop(timer_id, None)
        if started is None:
            self.warning("Timer not found", timer_id=timer_id)
            return 0.0

        elapsed_ms = (datetime.now().timestamp() - started) * 1000.0
        self.info("Timer completed", operation=operation or "operation", timer_id=timer_id, elapsed_ms=round(elapsed_ms, 2))
        return elapsed_ms


def get_logger(component_name: str, log_file: Path | None = None) -> VNALogger:
    """
    Get or create a logger instance for a component.

    Examples
    --------
    >>> logger = get_logger("magnitude_processor")
    >>> logger.info("Processor initialized")
    """
    cache_key = f"{component_name}|{log_file}"
    logger = VNALogger._loggers.get(cache_key)
    if logger is None:
        logger = VNALogger(component_name, log_file)
        VNALogger._loggers[cache_key] = logger
    return logger


def get_component_logger(component_path: str) -> VNALogger:
    """
    Create a logger with a component name derived from a file path.

    The base name of the file (without extension) is used, with a few
    opinionated adjustments for readability.
    """
    path = Path(component_path)
    component = path.stem

    if "processor" in component and not component.endswith("_processor"):
        component = f"{component}_processor"
    elif path.parent.name in {"websocket", "acquisition", "settings"}:
        component = f"{path.parent.name}_{component}"

    return get_logger(component)


def setup_logging(log_level: str = "INFO", log_dir: Path | None = None) -> None:
    """
    Configure application-wide logging defaults.

    Parameters
    ----------
    log_level:
        One of {"DEBUG","INFO","WARNING","ERROR","CRITICAL"} (case-insensitive).
    log_dir:
        If provided, creates a rotating file per component on demand.
    """
    level_name = log_level.upper()
    numeric_level = getattr(logging, level_name, None)
    if not isinstance(numeric_level, int):
        raise ValueError(f"Invalid log level: {log_level}")

    logging.getLogger("vna").setLevel(numeric_level)

    # Add global file handler for all logs if log_dir provided
    if log_dir:
        log_dir.mkdir(parents=True, exist_ok=True)
        global_log_file = log_dir / "vna_all.log"

        root = logging.getLogger()
        # Check if file handler already exists
        has_file_handler = any(isinstance(h, logging.FileHandler) for h in root.handlers)
        if not has_file_handler:
            file_handler = logging.FileHandler(global_log_file, encoding="utf-8")
            file_handler.setLevel(numeric_level)
            file_handler.setFormatter(logging.Formatter(
                "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
            ))
            root.addHandler(file_handler)

    # Touch the app logger (and its file) early to confirm configuration.
    log_path = (log_dir / "vna_system.log") if log_dir else None
    app_logger = get_logger("vna_system", log_path)
    app_logger.info("VNA System logging initialized", log_level=level_name, log_dir=str(log_dir) if log_dir else None)
