import inspect
import logging
import os
import sys
from fnmatch import fnmatch
from contextlib import contextmanager, nullcontext
from typing import Dict, Any, Optional

import logfire
import tomlkit
from logfire import LogfireLoggingHandler

from common.logging.config import LogFireSettings
from common.logging.formatter import get_formatter


def configure_logfire():
    logfire_config = LogFireSettings()

    # Disable Logfire when no token is provided or explicitly turned off for local dev.
    # This avoids runtime auth errors like "logfire auth" during docker-compose up.
    logfire_enabled_env = os.getenv("LOGFIRE_ENABLED", "").lower()
    logfire_disabled = logfire_enabled_env in {"0", "false", "no"}
    if logfire_disabled or not logfire_config.logfire_write_token:
        os.environ.setdefault("LOGFIRE_IGNORE_NO_CONFIG", "1")
        return

    logfire.instrument_pydantic(record="failure")

    stk = inspect.stack()[1]
    mod = inspect.getmodule(stk[0])
    mod_name = mod.__package__
    with open(os.path.join(mod_name, "pyproject.toml")) as f:
        project = tomlkit.parse(f.read())
        project_metadata = project.get("project", {})
        service_name = project_metadata.get("name")
        service_version = project_metadata.get("version")
        logfire.configure(
            scrubbing=False,
            service_name=service_name,
            service_version=service_version,
            token=logfire_config.logfire_write_token,
            environment=logfire_config.log_env,
            console=False,
            send_to_logfire=True if logfire_config.log_env != "None" else False,
        )


def _resolve_logger_level(name: str, default_level: int) -> int:
    """Apply service-specific log level overrides when available."""

    override = os.getenv("STREAM_PROCESSING_LOG_LEVEL", "").upper()
    if override and name.startswith("stream_processing_service"):
        return getattr(logging, override, default_level)

    api_override = os.getenv("TLDR_API_LOG_LEVEL", "WARNING").upper()
    if name.startswith("tldr_api"):
        return getattr(logging, api_override, logging.WARNING)
    return default_level


def get_logger(name: str, level=logging.INFO) -> logging.Logger:
    """
    Get a configured logger with the given name and level.

    Args:
        name: The name of the logger
        level: The logging level (default: INFO)

    Returns:
        A configured logger instance
    """
    resolved_level = _resolve_logger_level(name, level)

    logger = logging.getLogger(name)
    logger.setLevel(resolved_level)

    # Only add handlers if they don't exist to prevent duplicate handlers
    if not logger.handlers:
        formatter = get_formatter()
        ignore_logfire = os.getenv("LOGFIRE_IGNORE_NO_CONFIG", "").lower() in {
            "1",
            "true",
            "yes",
        }

        if not ignore_logfire:
            handler = LogfireLoggingHandler(level=resolved_level)
            handler.setLevel(resolved_level)
            handler.setFormatter(formatter)
            logger.addHandler(handler)

        std_out_handler = logging.StreamHandler(sys.stdout)
        std_out_handler.setFormatter(formatter)
        std_out_handler.setLevel(resolved_level)
        logger.addHandler(std_out_handler)

    return logger


# Reserved LogRecord attributes that cannot be used in `extra` dict
# See: https://docs.python.org/3/library/logging.html#logrecord-attributes
_RESERVED_LOG_ATTRIBUTES = frozenset(
    {
        "name",
        "msg",
        "args",
        "created",
        "filename",
        "funcName",
        "levelname",
        "levelno",
        "lineno",
        "module",
        "msecs",
        "pathname",
        "process",
        "processName",
        "relativeCreated",
        "thread",
        "threadName",
        "exc_info",
        "exc_text",
        "stack_info",
        "message",
    }
)

_TRUTHY_VALUES = frozenset({"1", "true", "yes", "on"})


def _filter_reserved_keys(attrs: Dict[str, Any]) -> Dict[str, Any]:
    """Filter out reserved LogRecord attribute names to prevent KeyError."""
    return {k: v for k, v in attrs.items() if k not in _RESERVED_LOG_ATTRIBUTES}


def _parse_pattern_env(var_name: str) -> tuple[str, ...]:
    raw = os.getenv(var_name, "")
    if not raw:
        return ()
    return tuple(part.strip() for part in raw.split(",") if part.strip())


def _matches_any_pattern(name: str, patterns: tuple[str, ...]) -> bool:
    return any(fnmatch(name, pattern) for pattern in patterns)


def _should_emit_low_level_span(name: str) -> bool:
    enabled = os.getenv("TLDR_ENABLE_LOW_LEVEL_SPANS", "1").lower() in _TRUTHY_VALUES
    allow_patterns = _parse_pattern_env("TLDR_LOW_LEVEL_SPAN_ALLOW")
    deny_patterns = _parse_pattern_env("TLDR_LOW_LEVEL_SPAN_DENY")

    if _matches_any_pattern(name, deny_patterns):
        return False
    if _matches_any_pattern(name, allow_patterns):
        return True
    return enabled


@contextmanager
def span(
    logger: logging.Logger,
    name: str,
    attributes: Optional[Dict[str, Any]] = None,
    *,
    log_entry: bool = True,
    log_exit: bool = True,
    low_level: bool = False,
):
    """
    Context manager for creating a logfire span to track execution of a code block.

    Args:
        logger: The logger instance to use
        name: The name of the span
        attributes: Optional dictionary of attributes to add to the span
        log_entry: Whether to emit a "Started" log message (default: True)
        log_exit: Whether to emit a "Completed" log message (default: True)
        low_level: Whether this span is considered high-frequency internal telemetry

    Example:
        ```python
        logger = get_logger(__name__)

        with span(logger, "process_data", {"user_id": 123}):
            # Code to process data
            logger.info("Processing data")
        ```
    """
    if attributes is None:
        attributes = {}

    ignore_logfire = os.getenv("LOGFIRE_IGNORE_NO_CONFIG", "").lower() in {
        "1",
        "true",
        "yes",
    }
    suppress_low_level = low_level and not _should_emit_low_level_span(name)
    span_context = (
        nullcontext()
        if ignore_logfire or suppress_low_level
        else logfire.span(name, **attributes)
    )

    if suppress_low_level:
        log_entry = False
        log_exit = False

    # Filter reserved keys before passing to logger.error/info
    safe_attrs = _filter_reserved_keys(attributes)

    with span_context:
        if log_entry and logger.isEnabledFor(logging.INFO):
            logger.info(f"Started {name}", extra=safe_attrs)
        try:
            yield
        except Exception as e:
            logger.error(
                f"Error in {name}: {str(e)}", extra={"error": str(e), **safe_attrs}
            )
            raise
        finally:
            if log_exit and logger.isEnabledFor(logging.INFO):
                logger.info(f"Completed {name}", extra=safe_attrs)
