"""Strategy factory for OpenClaw Trading.

This module provides the StrategyFactory class for creating strategy instances
from configuration and the create_strategy convenience function.
"""

from typing import Any, Dict, Optional, Type, TypeVar, Union

from openclaw.strategy.base import Strategy, StrategyParameters
from openclaw.strategy.buy import BuyParameters, BuyStrategy
from openclaw.strategy.registry import (
    StrategyNotFoundError,
    get_strategy_class,
    get_strategy_info,
    is_strategy_registered,
)
from openclaw.strategy.sell import SellParameters, SellStrategy
from openclaw.strategy.select import SelectParameters, SelectStrategy
from openclaw.utils.logging import get_logger

T = TypeVar("T", bound=Strategy)


class StrategyFactoryError(Exception):
    """Raised when strategy factory operation fails."""

    pass


class StrategyConfigurationError(Exception):
    """Raised when strategy configuration is invalid."""

    pass


class StrategyFactory:
    """Factory for creating strategy instances.

    The factory supports creating strategies by name (from registry)
    or by class. It handles parameter initialization and validation.

    Example:
        # Register a strategy
        @register_strategy(name="my_strategy")
        class MyStrategy(BuyStrategy):
            ...

        # Create instance via factory
        factory = StrategyFactory()
        strategy = factory.create("my_strategy", parameters={"max_position_size": 0.2})

        # Or create from config dict
        config = {
            "name": "my_strategy",
            "parameters": {"max_position_size": 0.2}
        }
        strategy = factory.create_from_config(config)
    """

    def __init__(self) -> None:
        """Initialize the strategy factory."""
        self.logger = get_logger("strategy.factory")

    def create(
        self,
        name: str,
        parameters: Optional[Dict[str, Any]] = None,
        description: str = "",
        strategy_class: Optional[Type[Strategy]] = None,
    ) -> Strategy:
        """Create a strategy instance.

        Args:
            name: Strategy name (instance name, not class name)
            parameters: Strategy parameters dictionary
            description: Strategy description
            strategy_class: Optional strategy class to use (bypasses registry)

        Returns:
            Strategy instance

        Raises:
            StrategyFactoryError: If strategy creation fails
        """
        try:
            # Get strategy class from registry or use provided class
            actual_class: Type[Strategy]
            if strategy_class is None:
                actual_class = get_strategy_class(name)
            else:
                actual_class = strategy_class

            # Determine parameter class from strategy type
            param_class = self._get_parameter_class(actual_class)

            # Create parameters object
            params = None
            if parameters and param_class:
                try:
                    params = param_class(**parameters)
                except Exception as e:
                    raise StrategyConfigurationError(
                        f"Invalid parameters for strategy '{name}': {e}"
                    )

            # Create strategy instance
            strategy = actual_class(
                name=name,
                parameters=params,
                description=description,
            )

            self.logger.debug(f"Created strategy instance: {name}")
            return strategy

        except StrategyNotFoundError:
            raise StrategyFactoryError(f"Strategy '{name}' not found in registry")
        except Exception as e:
            raise StrategyFactoryError(f"Failed to create strategy '{name}': {e}")

    def create_from_config(self, config: Dict[str, Any]) -> Strategy:
        """Create a strategy from a configuration dictionary.

        Args:
            config: Configuration dictionary with keys:
                - name: Strategy name
                - strategy_type: Optional strategy type/class name
                - parameters: Optional parameters dictionary
                - description: Optional description

        Returns:
            Strategy instance

        Raises:
            StrategyFactoryError: If configuration is invalid
        """
        if not isinstance(config, dict):
            raise StrategyConfigurationError("Config must be a dictionary")

        name = config.get("name")
        if not name:
            raise StrategyConfigurationError("Config must contain 'name' field")

        # Get strategy class if specified
        strategy_class: Optional[Type[Strategy]] = None
        strategy_type = config.get("strategy_type")
        if strategy_type:
            try:
                strategy_class = get_strategy_class(strategy_type)
            except StrategyNotFoundError:
                pass  # Will try to use name as class

        return self.create(
            name=name,
            parameters=config.get("parameters"),
            description=config.get("description", ""),
            strategy_class=strategy_class,
        )

    def create_buy_strategy(
        self,
        name: str,
        parameters: Optional[Dict[str, Any]] = None,
        description: str = "",
        strategy_class: Optional[Type[BuyStrategy]] = None,
    ) -> BuyStrategy:
        """Create a buy strategy instance.

        Args:
            name: Strategy name
            parameters: Buy strategy parameters
            description: Strategy description
            strategy_class: Optional buy strategy class

        Returns:
            BuyStrategy instance
        """
        strategy = self.create(name, parameters, description, strategy_class)

        if not isinstance(strategy, BuyStrategy):
            raise StrategyFactoryError(f"Strategy '{name}' is not a BuyStrategy")

        return strategy

    def create_sell_strategy(
        self,
        name: str,
        parameters: Optional[Dict[str, Any]] = None,
        description: str = "",
        strategy_class: Optional[Type[SellStrategy]] = None,
    ) -> SellStrategy:
        """Create a sell strategy instance.

        Args:
            name: Strategy name
            parameters: Sell strategy parameters
            description: Strategy description
            strategy_class: Optional sell strategy class

        Returns:
            SellStrategy instance
        """
        strategy = self.create(name, parameters, description, strategy_class)

        if not isinstance(strategy, SellStrategy):
            raise StrategyFactoryError(f"Strategy '{name}' is not a SellStrategy")

        return strategy

    def create_select_strategy(
        self,
        name: str,
        parameters: Optional[Dict[str, Any]] = None,
        description: str = "",
        strategy_class: Optional[Type[SelectStrategy]] = None,
    ) -> SelectStrategy:
        """Create a select strategy instance.

        Args:
            name: Strategy name
            parameters: Select strategy parameters
            description: Strategy description
            strategy_class: Optional select strategy class

        Returns:
            SelectStrategy instance
        """
        strategy = self.create(name, parameters, description, strategy_class)

        if not isinstance(strategy, SelectStrategy):
            raise StrategyFactoryError(f"Strategy '{name}' is not a SelectStrategy")

        return strategy

    def _get_parameter_class(
        self, strategy_class: Type[Strategy]
    ) -> Optional[Type[StrategyParameters]]:
        """Get the appropriate parameter class for a strategy type.

        Args:
            strategy_class: Strategy class

        Returns:
            Parameter class or None
        """
        if issubclass(strategy_class, BuyStrategy):
            return BuyParameters
        elif issubclass(strategy_class, SellStrategy):
            return SellParameters
        elif issubclass(strategy_class, SelectStrategy):
            return SelectParameters
        return StrategyParameters

    def get_available_strategies(self) -> list[str]:
        """Get list of available strategy names.

        Returns:
            List of registered strategy names
        """
        from openclaw.strategy.registry import get_registered_strategies

        return get_registered_strategies()

    def get_strategy_details(self, name: str) -> dict[str, Any]:
        """Get detailed information about a strategy.

        Args:
            name: Strategy name

        Returns:
            Strategy details dictionary
        """
        return get_strategy_info(name)

    def is_available(self, name: str) -> bool:
        """Check if a strategy is available.

        Args:
            name: Strategy name

        Returns:
            True if strategy is registered
        """
        return is_strategy_registered(name)


# Global factory instance
_default_factory: Optional[StrategyFactory] = None


def get_factory() -> StrategyFactory:
    """Get the default strategy factory instance."""
    global _default_factory
    if _default_factory is None:
        _default_factory = StrategyFactory()
    return _default_factory


def create_strategy(
    name: str,
    parameters: Optional[Dict[str, Any]] = None,
    description: str = "",
    strategy_class: Optional[Type[Strategy]] = None,
) -> Strategy:
    """Convenience function to create a strategy instance.

    This is a shortcut for StrategyFactory().create()

    Args:
        name: Strategy name
        parameters: Strategy parameters
        description: Strategy description
        strategy_class: Optional strategy class to use (bypasses registry)

    Returns:
        Strategy instance
    """
    return get_factory().create(name, parameters, description, strategy_class)


def create_strategy_from_config(config: Dict[str, Any]) -> Strategy:
    """Convenience function to create a strategy from config.

    This is a shortcut for StrategyFactory().create_from_config()

    Args:
        config: Strategy configuration dictionary

    Returns:
        Strategy instance
    """
    return get_factory().create_from_config(config)
