"""
Instrumentor Registry for auto-instrumentation.

The registry provides a central location to register and activate
instrumentors. It supports both explicit registration and automatic
discovery via Python entry points.
"""

import logging
import sys
from typing import Dict, List, Optional, Type

from .base import Instrumentor, MetricSchema

logger = logging.getLogger(__name__)


class InstrumentorRegistry:
    """
    Global registry for metric instrumentors.

    Instrumentors can be registered in two ways:
    1. Explicit registration:
        InstrumentorRegistry.register("openai", OpenAIInstrumentor)

    2. Entry point discovery (in pyproject.toml):
        [project.entry-points."mantisdk.instrumentors"]
        openai = "mantisdk.instrumentation.openai:OpenAIInstrumentor"

    Usage:
        # Enable a specific instrumentor
        mantisdk.instrument("openai")

        # Enable multiple instrumentors
        mantisdk.instrument("openai", "anthropic", "langchain")

        # Get list of available instrumentors
        InstrumentorRegistry.list_available()
    """

    _instrumentors: Dict[str, Type[Instrumentor]] = {}
    _active: Dict[str, Instrumentor] = {}
    _discovered: bool = False

    @classmethod
    def register(cls, name: str, instrumentor_cls: Type[Instrumentor]) -> None:
        """
        Register an instrumentor class.

        This is typically called at import time by instrumentor modules,
        or by entry point discovery.

        Args:
            name: Unique identifier for the instrumentor (e.g., "openai")
            instrumentor_cls: The instrumentor class to register
        """
        cls._instrumentors[name] = instrumentor_cls
        logger.debug(f"Registered instrumentor: {name}")

    @classmethod
    def instrument(cls, name: str, **kwargs) -> Optional[Instrumentor]:
        """
        Activate an instrumentor by name.

        Args:
            name: The instrumentor name to activate
            **kwargs: Additional arguments passed to the instrumentor constructor

        Returns:
            The activated instrumentor instance, or None if not available

        Raises:
            ValueError: If the instrumentor name is not registered
        """
        # Auto-discover instrumentors if not already done
        if not cls._discovered:
            cls.discover()

        if name not in cls._instrumentors:
            available = list(cls._instrumentors.keys())
            raise ValueError(f"Unknown instrumentor: {name}. Available: {available}")

        # Return existing instance if already active
        if name in cls._active:
            return cls._active[name]

        # Create and activate new instance
        instrumentor_cls = cls._instrumentors[name]
        instance = instrumentor_cls(**kwargs)

        # Check if target library is available
        if not instance.is_available():
            logger.warning(f"Instrumentor '{name}' target library is not available")
            return None

        try:
            instance.instrument()
            cls._active[name] = instance
            logger.info(f"Activated instrumentor: {name}")
            return instance
        except Exception as e:
            logger.error(f"Failed to activate instrumentor '{name}': {e}")
            return None

    @classmethod
    def uninstrument(cls, name: str) -> bool:
        """
        Deactivate an instrumentor by name.

        Args:
            name: The instrumentor name to deactivate

        Returns:
            True if successfully deactivated, False otherwise
        """
        if name not in cls._active:
            logger.debug(f"Instrumentor '{name}' is not active")
            return False

        instance = cls._active[name]
        try:
            instance.uninstrument()
            del cls._active[name]
            logger.info(f"Deactivated instrumentor: {name}")
            return True
        except Exception as e:
            logger.error(f"Failed to deactivate instrumentor '{name}': {e}")
            return False

    @classmethod
    def uninstrument_all(cls) -> None:
        """Deactivate all active instrumentors."""
        for name in list(cls._active.keys()):
            cls.uninstrument(name)

    @classmethod
    def discover(cls) -> None:
        """
        Auto-discover instrumentors via Python entry points.

        Entry points should be defined in pyproject.toml:
            [project.entry-points."mantisdk.instrumentors"]
            openai = "mantisdk.instrumentation.openai:OpenAIInstrumentor"
        """
        if cls._discovered:
            return

        try:
            # Python 3.10+ has importlib.metadata.entry_points with group param
            if sys.version_info >= (3, 10):
                from importlib.metadata import entry_points

                eps = entry_points(group="mantisdk.instrumentors")
            else:
                # Python 3.9 compatibility
                from importlib.metadata import entry_points

                eps = entry_points().get("mantisdk.instrumentors", [])

            for ep in eps:
                try:
                    instrumentor_cls = ep.load()
                    cls.register(ep.name, instrumentor_cls)
                except Exception as e:
                    logger.warning(f"Failed to load instrumentor '{ep.name}': {e}")

        except ImportError:
            logger.debug("importlib.metadata not available, skipping entry point discovery")

        cls._discovered = True

    @classmethod
    def list_available(cls) -> List[str]:
        """
        List all registered instrumentor names.

        Returns:
            List of instrumentor names
        """
        if not cls._discovered:
            cls.discover()
        return list(cls._instrumentors.keys())

    @classmethod
    def list_active(cls) -> List[str]:
        """
        List all currently active instrumentor names.

        Returns:
            List of active instrumentor names
        """
        return list(cls._active.keys())

    @classmethod
    def get_metric_schemas(cls) -> Dict[str, Dict[str, MetricSchema]]:
        """
        Get metric schemas from all registered instrumentors.

        Returns:
            Dict mapping instrumentor names to their metric schemas
        """
        if not cls._discovered:
            cls.discover()

        schemas = {}
        for name, instrumentor_cls in cls._instrumentors.items():
            instance = instrumentor_cls()
            schema = instance.get_metric_schema()
            if schema:
                schemas[name] = schema
        return schemas

    @classmethod
    def is_active(cls, name: str) -> bool:
        """
        Check if an instrumentor is currently active.

        Args:
            name: The instrumentor name to check

        Returns:
            True if the instrumentor is active
        """
        return name in cls._active

    @classmethod
    def get(cls, name: str) -> Optional[Instrumentor]:
        """
        Get an active instrumentor instance by name.

        Args:
            name: The instrumentor name

        Returns:
            The instrumentor instance if active, None otherwise
        """
        return cls._active.get(name)


# Convenience function for module-level access
def instrument(*names: str, **kwargs) -> List[Optional[Instrumentor]]:
    """
    Activate one or more instrumentors by name.

    Args:
        *names: Instrumentor names to activate
        **kwargs: Additional arguments passed to instrumentor constructors

    Returns:
        List of activated instrumentor instances (None for failed activations)

    Examples:
        # Enable OpenAI instrumentation
        mantisdk.instrument("openai")

        # Enable multiple instrumentors
        mantisdk.instrument("openai", "anthropic", "langchain")
    """
    return [InstrumentorRegistry.instrument(name, **kwargs) for name in names]


def uninstrument(*names: str) -> None:
    """
    Deactivate one or more instrumentors by name.

    Args:
        *names: Instrumentor names to deactivate

    Examples:
        mantisdk.uninstrument("openai")
    """
    for name in names:
        InstrumentorRegistry.uninstrument(name)
