"""Strategy registration system for OpenClaw Trading.

This module provides the @register_strategy decorator and registry management
for dynamic strategy discovery and loading.
"""

from typing import Any, Callable, Dict, List, Optional, Type, TypeVar

from openclaw.strategy.base import Strategy

T = TypeVar("T", bound=Type[Strategy])

# Global strategy registry
_strategy_registry: Dict[str, Type[Strategy]] = {}
_strategy_metadata: Dict[str, Dict[str, Any]] = {}


class StrategyRegistrationError(Exception):
    """Raised when strategy registration fails."""

    pass


class StrategyNotFoundError(Exception):
    """Raised when a requested strategy is not found in the registry."""

    pass


def register_strategy(
    name: Optional[str] = None,
    description: str = "",
    tags: Optional[List[str]] = None,
    author: str = "",
    version: str = "1.0.0",
) -> Callable[[T], T]:
    """Decorator to register a strategy class.

    This decorator registers a strategy class in the global registry,
    making it available for dynamic instantiation.

    Args:
        name: Strategy name (defaults to class name)
        description: Strategy description
        tags: List of strategy tags (e.g., ["momentum", "mean-reversion"])
        author: Strategy author
        version: Strategy version

    Returns:
        Decorator function that registers the strategy class

    Example:
        @register_strategy(
            name="sma_crossover",
            description="Simple moving average crossover strategy",
            tags=["trend", "momentum"],
        )
        class SmaCrossoverStrategy(BuyStrategy):
            ...
    """

    def decorator(strategy_class: T) -> T:
        strategy_name = name or strategy_class.__name__

        # Check for duplicate registration
        if strategy_name in _strategy_registry:
            raise StrategyRegistrationError(
                f"Strategy '{strategy_name}' is already registered. "
                f"Use a different name or unregister the existing strategy first."
            )

        # Validate that the class is a Strategy subclass
        if not issubclass(strategy_class, Strategy):
            raise StrategyRegistrationError(
                f"Strategy '{strategy_name}' must inherit from Strategy"
            )

        # Register the strategy
        _strategy_registry[strategy_name] = strategy_class
        _strategy_metadata[strategy_name] = {
            "name": strategy_name,
            "class_name": strategy_class.__name__,
            "module": strategy_class.__module__,
            "description": description,
            "tags": tags or [],
            "author": author,
            "version": version,
        }

        return strategy_class

    return decorator


def unregister_strategy(name: str) -> bool:
    """Unregister a strategy from the registry.

    Args:
        name: Strategy name to unregister

    Returns:
        True if strategy was unregistered, False if not found
    """
    if name in _strategy_registry:
        del _strategy_registry[name]
        del _strategy_metadata[name]
        return True
    return False


def get_strategy_class(name: str) -> Type[Strategy]:
    """Get a strategy class by name.

    Args:
        name: Registered strategy name

    Returns:
        Strategy class

    Raises:
        StrategyNotFoundError: If strategy is not registered
    """
    if name not in _strategy_registry:
        raise StrategyNotFoundError(f"Strategy '{name}' not found in registry")
    return _strategy_registry[name]


def get_registered_strategies() -> List[str]:
    """Get list of all registered strategy names.

    Returns:
        List of strategy names
    """
    return list(_strategy_registry.keys())


def get_strategy_info(name: str) -> Dict[str, Any]:
    """Get metadata for a registered strategy.

    Args:
        name: Strategy name

    Returns:
        Strategy metadata dictionary

    Raises:
        StrategyNotFoundError: If strategy is not registered
    """
    if name not in _strategy_metadata:
        raise StrategyNotFoundError(f"Strategy '{name}' not found in registry")
    return _strategy_metadata[name].copy()


def get_strategies_by_tag(tag: str) -> List[str]:
    """Get strategy names filtered by tag.

    Args:
        tag: Tag to filter by

    Returns:
        List of strategy names with the given tag
    """
    return [
        name
        for name, metadata in _strategy_metadata.items()
        if tag in metadata.get("tags", [])
    ]


def is_strategy_registered(name: str) -> bool:
    """Check if a strategy is registered.

    Args:
        name: Strategy name to check

    Returns:
        True if strategy is registered
    """
    return name in _strategy_registry


def clear_registry() -> None:
    """Clear all registered strategies.

    WARNING: This removes all strategies from the registry.
    Use with caution, primarily for testing.
    """
    _strategy_registry.clear()
    _strategy_metadata.clear()


def get_registry_stats() -> Dict[str, Any]:
    """Get registry statistics.

    Returns:
        Dictionary with registry statistics
    """
    all_tags = set()
    for metadata in _strategy_metadata.values():
        all_tags.update(metadata.get("tags", []))

    return {
        "total_strategies": len(_strategy_registry),
        "strategy_names": list(_strategy_registry.keys()),
        "unique_tags": sorted(list(all_tags)),
    }


# Auto-discovery helper
def discover_strategies(module_path: str) -> List[str]:
    """Discover and register strategies from a module.

    This function imports a module and registers any Strategy subclasses found.

    Args:
        module_path: Python module path (e.g., "my_strategies.custom")

    Returns:
        List of discovered strategy names
    """
    import importlib
    import inspect

    try:
        module = importlib.import_module(module_path)
    except ImportError as e:
        raise StrategyRegistrationError(f"Failed to import module '{module_path}': {e}")

    discovered = []
    for name, obj in inspect.getmembers(module):
        if (
            inspect.isclass(obj)
            and issubclass(obj, Strategy)
            and obj is not Strategy
            and not getattr(obj, "__abstractmethods__", None)
        ):
            # Check if already registered
            if name not in _strategy_registry:
                # Auto-register with default metadata
                _strategy_registry[name] = obj
                _strategy_metadata[name] = {
                    "name": name,
                    "class_name": obj.__name__,
                    "module": obj.__module__,
                    "description": obj.__doc__ or "",
                    "tags": [],
                    "author": "",
                    "version": "1.0.0",
                }
                discovered.append(name)

    return discovered
