"""Factor base class for OpenClaw Trading.

This module provides the abstract Factor base class that defines the interface
for all trading factors in the factor market system.
"""

from abc import ABC, abstractmethod
from typing import Any, Optional

from openclaw.factor.types import (
    FactorCategory,
    FactorContext,
    FactorMetadata,
    FactorResult,
    FactorType,
)
from openclaw.utils.logging import get_logger


class Factor(ABC):
    """Abstract base class for all trading factors.

    Factors are reusable components that generate trading signals based on
    market data. They can be bought, sold, and unlocked in the factor market.

    Args:
        metadata: Factor metadata including name, type, category, price
        parameters: Factor-specific parameters
    """

    def __init__(
        self,
        metadata: FactorMetadata,
        parameters: Optional[dict[str, Any]] = None,
    ):
        self.metadata = metadata
        self.parameters = parameters or {}
        self.logger = get_logger(f"factor.{metadata.name}")

        # State tracking
        self._initialized = False
        self._unlocked = False
        self._usage_count = 0

    @property
    def id(self) -> str:
        """Generate unique factor identifier."""
        return f"{self.metadata.factor_type.value}_{self.metadata.name.lower().replace(' ', '_')}"

    @property
    def is_initialized(self) -> bool:
        """Check if factor has been initialized."""
        return self._initialized

    @property
    def is_unlocked(self) -> bool:
        """Check if factor is unlocked and usable."""
        return self._unlocked

    @property
    def is_free(self) -> bool:
        """Check if factor is free (basic category)."""
        return self.metadata.category == FactorCategory.BASIC

    @property
    def usage_count(self) -> int:
        """Get number of times factor has been used."""
        return self._usage_count

    def initialize(self) -> None:
        """Initialize the factor.

        This method should be called before using the factor.
        It sets up internal state and calls on_init for subclass setup.
        """
        if self._initialized:
            self.logger.warning(f"Factor {self.metadata.name} already initialized")
            return

        self.logger.info(f"Initializing factor: {self.metadata.name}")

        # Call subclass initialization
        self.on_init()

        self._initialized = True
        self.logger.info(f"Factor {self.metadata.name} initialized successfully")

    def unlock(self) -> None:
        """Unlock the factor for use.

        This should be called after purchasing the factor.
        """
        if self._unlocked:
            self.logger.warning(f"Factor {self.metadata.name} already unlocked")
            return

        self._unlocked = True
        self.logger.info(f"Factor {self.metadata.name} unlocked")

    def lock(self) -> None:
        """Lock the factor, preventing its use."""
        self._unlocked = False
        self.logger.info(f"Factor {self.metadata.name} locked")

    def evaluate(self, context: FactorContext) -> Optional[FactorResult]:
        """Evaluate the factor against market data.

        This is the main entry point for factor evaluation. It validates
        state, calls on_evaluate for processing, and tracks usage.

        Args:
            context: Factor context with market data, positions, etc.

        Returns:
            FactorResult object if factor generates a signal, None otherwise
        """
        if not self._initialized:
            raise RuntimeError(
                f"Factor {self.metadata.name} not initialized. Call initialize() first."
            )

        if not self._unlocked:
            raise RuntimeError(
                f"Factor {self.metadata.name} is locked. Purchase to unlock."
            )

        # Check minimum data requirement
        if len(context.data) < self.metadata.min_data_points:
            self.logger.debug(
                f"Insufficient data: {len(context.data)} < {self.metadata.min_data_points}"
            )
            return None

        # Call subclass implementation
        result = self.on_evaluate(context)

        if result:
            self._usage_count += 1
            self.on_result_generated(result)

        return result

    # Callback methods for subclasses to override

    def on_init(self) -> None:
        """Called when factor is initialized.

        Subclasses can override this to perform setup tasks like:
        - Loading indicator configurations
        - Setting up internal state
        - Validating parameters
        """
        pass

    @abstractmethod
    def on_evaluate(self, context: FactorContext) -> Optional[FactorResult]:
        """Evaluate the factor against market data.

        This is the main factor logic method that subclasses must implement.

        Args:
            context: Factor context with market data, positions, etc.

        Returns:
            FactorResult object if factor generates a signal, None otherwise
        """
        pass

    def on_result_generated(self, result: FactorResult) -> None:
        """Called when a factor result is generated.

        Subclasses can override this to react to result generation,
        such as logging or additional validation.

        Args:
            result: The generated factor result
        """
        self.logger.debug(
            f"Factor result: {result.signal.value} for {result.symbol} "
            f"(confidence: {result.confidence:.2f})"
        )

    def get_state(self) -> dict[str, Any]:
        """Get current factor state.

        Returns:
            Dictionary containing factor state
        """
        return {
            "id": self.id,
            "name": self.metadata.name,
            "type": self.metadata.factor_type.value,
            "category": self.metadata.category.value,
            "price": self.metadata.price,
            "initialized": self._initialized,
            "unlocked": self._unlocked,
            "usage_count": self._usage_count,
        }

    def reset(self) -> None:
        """Reset factor state.

        This method resets the factor to its initial state.
        Subclasses should call super().reset() and then reset their own state.
        """
        self._usage_count = 0
        self.logger.info(f"Factor {self.metadata.name} state reset")

    def __repr__(self) -> str:
        """String representation of the factor."""
        return (
            f"{self.__class__.__name__}("
            f"name='{self.metadata.name}', "
            f"type={self.metadata.factor_type.value}, "
            f"category={self.metadata.category.value}, "
            f"price=${self.metadata.price:.2f}, "
            f"unlocked={self._unlocked}"
            f")"
        )

    def __enter__(self) -> "Factor":
        """Context manager entry."""
        self.initialize()
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        """Context manager exit."""
        pass


class BuyFactor(Factor):
    """Base class for buy signal factors.

    Buy factors evaluate market conditions and generate buy signals
    when favorable entry conditions are met.
    """

    def __init__(
        self,
        name: str,
        description: str,
        category: FactorCategory = FactorCategory.BASIC,
        price: float = 0.0,
        parameters: Optional[dict[str, Any]] = None,
    ):
        metadata = FactorMetadata(
            name=name,
            description=description,
            factor_type=FactorType.BUY,
            category=category,
            price=price,
            parameters=parameters or {},
        )
        super().__init__(metadata, parameters)


class SellFactor(Factor):
    """Base class for sell signal factors.

    Sell factors evaluate market conditions and generate sell signals
    when favorable exit conditions are met.
    """

    def __init__(
        self,
        name: str,
        description: str,
        category: FactorCategory = FactorCategory.BASIC,
        price: float = 0.0,
        parameters: Optional[dict[str, Any]] = None,
    ):
        metadata = FactorMetadata(
            name=name,
            description=description,
            factor_type=FactorType.SELL,
            category=category,
            price=price,
            parameters=parameters or {},
        )
        super().__init__(metadata, parameters)


class SelectFactor(Factor):
    """Base class for stock selection factors.

    Select factors evaluate multiple stocks and generate selection
    signals for portfolio construction.
    """

    def __init__(
        self,
        name: str,
        description: str,
        category: FactorCategory = FactorCategory.BASIC,
        price: float = 0.0,
        parameters: Optional[dict[str, Any]] = None,
    ):
        metadata = FactorMetadata(
            name=name,
            description=description,
            factor_type=FactorType.SELECT,
            category=category,
            price=price,
            parameters=parameters or {},
        )
        super().__init__(metadata, parameters)
