# Copyright (c) Microsoft. All rights reserved.

from __future__ import annotations

import logging
import os
import platform
import sys
import warnings
from logging.config import dictConfig
from typing import Any, Dict, Optional

from rich.console import Console

__all__ = ["setup", "configure_logger", "setup_module"]


def configure_logger(level: int = logging.INFO, name: str = "mantisdk") -> logging.Logger:
    """Create or reset a namespaced logger with a consistent console format.

    This helper clears any previously attached handlers before binding a single
    `StreamHandler` that writes to standard output. The resulting logger does
    not propagate to the root logger, preventing duplicate log emission when
    applications compose multiple logging configurations.

    !!! danger

        This function is deprecated in favor of [`setup_logging`][mantisdk.setup_logging].

    Args:
        level: Logging level applied both to the logger and the installed
            handler. Defaults to `logging.INFO`.
        name: Dotted path for the logger instance. Defaults to
            `"mantisdk"`.

    Returns:
        Configured logger instance ready for immediate use.

    Examples:
        ```python
        from mantisdk import configure_logger

        logger = configure_logger(level=logging.INFO)
        logger.info("mantisdk is ready!")
        ```
    """
    warnings.warn("This function is deprecated in favor of `setup_logging`.", DeprecationWarning, stacklevel=2)

    return setup_module(level=level, name=name, console=True, color=True, propagate=False)


DEFAULT_FORMAT = "%(asctime)s [%(levelname)s] (Process-%(process)d %(name)s)   %(message)s"
DATE_FORMAT = "%H:%M:%S"


def _to_level_value(lvl: int | str) -> int:
    if isinstance(lvl, int):
        return lvl
    val = getattr(logging, str(lvl).upper(), None)
    if val is None:
        raise ValueError(f"Invalid log level: {lvl}")
    return val


def _ensure_file_handler(
    logger: logging.Logger,
    filename: str,
    *,
    level: int,
    formatter: Optional[logging.Formatter],
) -> None:
    """Attach a FileHandler to `logger` for `filename` if it doesn't already exist."""
    abspath = os.path.abspath(filename)

    # Avoid duplicates
    for h in logger.handlers:
        if isinstance(h, logging.FileHandler) and getattr(h, "baseFilename", None) == abspath:
            return

    # Ensure directory exists
    dirname = os.path.dirname(abspath)
    if dirname:
        os.makedirs(dirname, exist_ok=True)

    fh = logging.FileHandler(abspath, encoding="utf-8")
    fh.setLevel(level)
    if formatter is not None:
        fh.setFormatter(formatter)
    else:
        fh.setFormatter(logging.Formatter(DEFAULT_FORMAT, DATE_FORMAT))

    logger.addHandler(fh)


def setup(
    level: int | str = "INFO",
    *,
    console: bool = True,
    color: bool | Dict[str, Any] = True,
    propagate: bool = False,
    disable_existing_loggers: bool = False,
    capture_warnings: bool = False,
    submodule_levels: Optional[dict[str, int | str]] = None,
    extra_handlers: Optional[list[logging.Handler]] = None,
    formatter: Optional[logging.Formatter] = None,
    apply_to: Optional[list[str]] = None,
    files: Optional[str | dict[str, str]] = None,
) -> None:
    """Configures logging for the `mantisdk` logger hierarchy.

    This function provides a one-stop setup utility for configuring the
    `mantisdk` root logger and optionally its submodules or external
    loggers. It supports console logging, colored rich output, per-submodule
    log levels, and optional handler/formatter injection.

    The setup is intentionally isolated: it does not modify the global root
    logger or loggers belonging to other libraries unless explicitly directed
    via `apply_to`.

    Args:
        level:
            Logging level for the base `mantisdk` logger. Accepts either
            an integer (e.g., `logging.DEBUG`) or a string level name
            (e.g., `"INFO"`). Defaults to `"INFO"`.
        console:
            Whether to attach a console handler to the logger. Defaults to
            `True`.
        color:
            Enables rich-formatted output using `RichHandler` when `True`
            or a configuration dict. If `False`, a plain text formatter is
            used instead. Defaults to `True`.
        propagate:
            Whether `mantisdk` logs should propagate to ancestor
            loggers. Defaults to `False`.
        disable_existing_loggers:
            Passed to `logging.config.dictConfig`. If `True`, disables all
            existing configured loggers before applying this configuration.
            Defaults to `False`.
        capture_warnings:
            If `True`, redirects Python `warnings` emitted via the `warnings`
            module into the logging system. Defaults to `False`.
        submodule_levels:
            Mapping of submodule logger names to logging levels. If a specified
            submodule level is more verbose than the base level, a warning is emitted.
        extra_handlers:
            A list of user-provided handlers to attach to the `mantisdk` logger.
            Handlers are added idempotently; duplicates are not reattached.
        formatter:
            A formatter to apply to any handler under `mantisdk` that does not
            already have one assigned. Useful for customizing output without overwriting
            formatters on custom handlers.
        apply_to:
            A list of additional logger names to configure identically to
            `mantisdk` base logger. Their handlers are replaced with copies of the base
            handlers, and propagation is disabled to avoid duplicate log emission.
        files:
            If a string, attach a FileHandler to the base `mantisdk` logger.
            If a dict, for each `(logger_name, filename)` pair, attach a FileHandler
            directly to that logger.
            Each file handler should use the logger's effective level at creation.

    Notes:
        * On Windows, this function forces UTF-8 mode in the console to prevent
          issues with rich output or special characters.
        * Submodule loggers can generate records below the handler's emission
          threshold. Whether such records appear depends on both the logger's
          level and the handler's level.
        * `apply_to` loggers inherit the same handlers but do not propagate
          upward, yielding isolated, consistent behavior.

    Examples:
        Basic setup:

        >>> setup()

        Enabling debug mode with no color:

        >>> setup(level="DEBUG", color=False)

        Overriding specific submodule levels:

        >>> setup(submodule_levels={"mantisdk.io": "DEBUG"})

        Attaching an additional file handler:

        >>> fh = logging.FileHandler("app.log")
        >>> setup(extra_handlers=[fh])
    """
    # Ensure UTF-8 encoding on Windows consoles
    # Note: This change does not fully represent support for execution under the windows system.
    # It only fixes console printing issues caused by special characters.
    # TODO: More comprehensive Windows support may be needed in the future.
    if platform.system() == "Windows":
        os.environ["PYTHONUTF8"] = "1"

    base_logger = setup_module(
        level,
        name="mantisdk",
        console=console,
        color=color,
        propagate=propagate,
        disable_existing_loggers=disable_existing_loggers,
    )

    base_level_value = base_logger.level

    # Apply user-provided formatter (only to handlers without one,
    # so we don't clobber custom extra_handlers)
    if formatter is not None:
        for h in base_logger.handlers:
            if h.formatter is None:
                h.setFormatter(formatter)

    # Attach user-provided handler(s) if any, idempotently
    if extra_handlers:
        for h in extra_handlers:
            if h not in base_logger.handlers:
                base_logger.addHandler(h)

    # Per-submodule levels
    if submodule_levels:
        for name, lvl in submodule_levels.items():
            sub_level = _to_level_value(lvl)

            # Emit a warning if submodule level is lower (more verbose) than the global/base level
            if sub_level < base_level_value:
                base_logger.warning(
                    "Submodule logger '%s' level %s (%s) is more verbose than base "
                    "logger level %s (%s). Records below the base level may still be "
                    "filtered out by handlers depending on their own levels.",
                    name,
                    lvl,
                    sub_level,
                    logging.getLevelName(base_level_value),
                    base_level_value,
                )

            # The logger will *create* records down to the logger's level, but a handler
            # with a higher level will still drop anything below its own threshold.
            # Effective emission is gated by both: record.level >= logger.level AND handler.level.
            logging.getLogger(name).setLevel(lvl)

    # Attach file handlers if requested
    if files is not None:
        if isinstance(files, str):
            # Single file for the entire `mantisdk` hierarchy.
            _ensure_file_handler(
                logger=base_logger,
                filename=files,
                level=base_level_value,
                formatter=formatter,
            )
        else:
            # Per-logger files
            for logger_name, filename in files.items():
                lg = logging.getLogger(logger_name)
                # Use the logger's *effective* level at creation time
                effective_level = lg.getEffectiveLevel()
                _ensure_file_handler(
                    logger=lg,
                    filename=filename,
                    level=effective_level,
                    formatter=formatter,
                )

    # Optionally apply the same handler setup to other loggers outside this module
    if apply_to:
        for name in apply_to:
            lg = logging.getLogger(name)
            # This removes any existing handlers so we don't duplicate output
            # and ensures these loggers share exactly the same handlers as base_logger.
            lg.handlers.clear()
            for h in base_logger.handlers:
                lg.addHandler(h)
            lg.setLevel(base_logger.level)
            # We've attached handlers directly to these loggers; if propagate
            # stayed True, records would bubble up to ancestor loggers and could be
            # emitted twice (here and on the parent/root). Setting False isolates them.
            lg.propagate = False

    # Optionally capture warnings
    if capture_warnings:
        logging.captureWarnings(True)


def setup_module(
    level: int | str = "INFO",
    *,
    name: str = "mantisdk",
    console: bool = True,
    color: bool | Dict[str, Any] = True,
    propagate: bool = False,
    disable_existing_loggers: bool = False,
) -> logging.Logger:
    """Initializes and returns the base logger for `mantisdk`.

    This function constructs and applies a `dictConfig` configuration for the
    logger hierarchy rooted at `name`. It supports either rich console
    formatting (via `RichHandler`) or plain text formatting, based on the
    `color` argument.

    Unlike [`setup_logging`][mantisdk.setup_logging], this function configures only a single logger namespace
    and does not attach extra handlers or submodule levels. It is primarily used
    internally by [`setup_logging`][mantisdk.setup_logging] but is also suitable for direct integration in
    custom logging workflows.
    """
    root_cfg: Dict[str, Any] = {
        "version": 1,
        "disable_existing_loggers": disable_existing_loggers,
        "loggers": {
            name: {
                "handlers": [],
                "level": level,
                "propagate": propagate,
            }
        },
        "handlers": {},
        "formatters": {},
    }

    # Choose formatter / handler definition
    if color is not False and console:
        # Console must be true to display colored outputs
        if isinstance(color, dict):
            rich_handler_config = color
        else:
            rich_handler_config: Dict[str, Any] = {
                "rich_tracebacks": False,
                "markup": False,
                "show_time": True,
                "show_path": True,
            }

            if not _has_width():
                # e.g., in a CI environment.
                rich_handler_config["console"] = Console(width=200)

        root_cfg["handlers"]["console"] = {
            "class": "rich.logging.RichHandler",
            "level": level,
            **rich_handler_config,
        }
        # RichHandler manages its own style; keep formatter None
    else:
        fmt_name = "plain"
        root_cfg["formatters"][fmt_name] = {
            "format": DEFAULT_FORMAT,
            "datefmt": DATE_FORMAT,
        }

        if console:
            root_cfg["handlers"]["console"] = {
                "class": "logging.StreamHandler",
                "level": level,
                "formatter": fmt_name,
            }

    # Attach selected handlers to mantisdk
    handler_names = list(root_cfg["handlers"].keys())
    root_cfg["loggers"][name]["handlers"] = handler_names

    # Apply dictConfig (this resets the logger handlers)
    dictConfig(root_cfg)

    return logging.getLogger(name)


def _has_width() -> bool:
    """Automatically determine whether the terminal has a width."""
    return sys.stdout.isatty()
