"""Sell strategy base class for OpenClaw Trading.

This module provides the SellStrategy abstract class that defines the interface
for sell-side trading strategies.
"""

from abc import abstractmethod
from typing import Any, Dict, List, Optional

import pandas as pd
from pydantic import BaseModel, Field, field_validator

from openclaw.strategy.base import Signal, SignalType, Strategy, StrategyContext, StrategyParameters


class SellParameters(StrategyParameters):
    """Parameters for sell strategies.

    Attributes:
        stop_loss_pct: Stop loss percentage (0.05 = 5%)
        take_profit_pct: Take profit percentage (0.10 = 10%)
        trailing_stop_pct: Trailing stop percentage
        min_confidence: Minimum confidence level to generate signal
        exit_threshold: Threshold for exit signal generation
    """

    stop_loss_pct: float = Field(default=0.05, ge=0.0, le=1.0, description="Stop loss percentage")
    take_profit_pct: float = Field(default=0.10, ge=0.0, description="Take profit percentage")
    trailing_stop_pct: Optional[float] = Field(default=None, ge=0.0, description="Trailing stop percentage")
    min_confidence: float = Field(default=0.5, ge=0.0, le=1.0, description="Minimum confidence to generate signal")
    exit_threshold: float = Field(default=0.0, description="Threshold for exit signal")

    @field_validator("stop_loss_pct")
    @classmethod
    def validate_stop_loss(cls, v: float) -> float:
        """Validate stop loss is reasonable."""
        if v > 0.5:
            return 0.5  # Cap at 50%
        return v


class SellStrategy(Strategy):
    """Abstract base class for sell-side trading strategies.

    Sell strategies generate sell signals based on market conditions,
    position state, and strategy-specific logic.

    Args:
        name: Strategy name identifier
        parameters: Sell strategy parameters
        description: Strategy description
    """

    def __init__(
        self,
        name: str,
        parameters: Optional[SellParameters] = None,
        description: str = "",
    ):
        super().__init__(name, parameters or SellParameters(), description)
        self.parameters: SellParameters = self.parameters  # Type hint for IDE

        # Track sell-specific state
        self._sell_signals_generated = 0
        self._positions_exited: List[Dict[str, Any]] = []
        self._highest_price_seen: Dict[str, float] = {}  # For trailing stops

    @property
    def sell_signals_generated(self) -> int:
        """Get count of sell signals generated."""
        return self._sell_signals_generated

    def on_bar(self, data: pd.Series, context: StrategyContext) -> Optional[Signal]:
        """Process a new bar and generate sell signal if conditions met.

        Args:
            data: Current bar data (OHLCV)
            context: Strategy context with positions, equity, etc.

        Returns:
            Sell signal if conditions are met, None otherwise
        """
        # Check if we have a position to sell
        if context.symbol not in context.positions:
            return None

        position = context.positions[context.symbol]

        # Update trailing stop tracking
        self._update_trailing_stop(context.symbol, data)

        # Check if we should generate a sell signal
        if not self._should_sell(data, context, position):
            return None

        # Calculate sell parameters
        confidence = self._calculate_sell_confidence(data, context, position)

        # Check minimum confidence
        if confidence < self.parameters.min_confidence:
            return None

        # Generate sell signal
        signal = Signal(
            signal_type=SignalType.SELL,
            symbol=context.symbol,
            price=self._get_exit_price(data),
            quantity=getattr(position, "quantity", None),
            confidence=confidence,
            metadata={
                "strategy": self.name,
                "threshold": self.parameters.exit_threshold,
                "stop_loss_triggered": self._check_stop_loss(data, position),
                "take_profit_triggered": self._check_take_profit(data, position),
                "indicators": self._get_signal_indicators(data),
            },
        )

        self._sell_signals_generated += 1
        return signal

    def _generate_signal_impl(self, context: StrategyContext) -> Optional[Signal]:
        """Generate sell signal based on current context.

        For sell strategies, this checks if current conditions warrant a sell.

        Args:
            context: Strategy context

        Returns:
            Sell signal if conditions are met, None otherwise
        """
        # This is a simplified implementation
        # Subclasses can override for more sophisticated logic
        if not context.market_data:
            return None

        # Check if we have a position to sell
        if context.symbol not in context.positions:
            return None

        position = context.positions[context.symbol]

        # Generate sell signal with default values
        signal = Signal(
            signal_type=SignalType.SELL,
            symbol=context.symbol,
            quantity=getattr(position, "quantity", None),
            confidence=self.parameters.min_confidence,
            metadata={"strategy": self.name, "generated_from": "context"},
        )

        self._sell_signals_generated += 1
        return signal

    @abstractmethod
    def _should_sell(self, data: pd.Series, context: StrategyContext, position: Any) -> bool:
        """Determine if strategy should generate a sell signal.

        Subclasses must implement this method to define their sell logic.

        Args:
            data: Current bar data
            context: Strategy context
            position: Current position

        Returns:
            True if sell conditions are met, False otherwise
        """
        pass

    @abstractmethod
    def _calculate_sell_confidence(self, data: pd.Series, context: StrategyContext, position: Any) -> float:
        """Calculate confidence level for sell signal.

        Subclasses must implement this to provide confidence scoring.

        Args:
            data: Current bar data
            context: Strategy context
            position: Current position

        Returns:
            Confidence level between 0.0 and 1.0
        """
        pass

    def _update_trailing_stop(self, symbol: str, data: pd.Series) -> None:
        """Update trailing stop tracking.

        Args:
            symbol: Trading symbol
            data: Current bar data
        """
        high = data.get("high")
        if high is None:
            return

        if symbol not in self._highest_price_seen or high > self._highest_price_seen[symbol]:
            self._highest_price_seen[symbol] = high

    def _check_stop_loss(self, data: pd.Series, position: Any) -> bool:
        """Check if stop loss is triggered.

        Args:
            data: Current bar data
            position: Current position

        Returns:
            True if stop loss is triggered
        """
        entry_price = getattr(position, "entry_price", None)
        current_price = data.get("low", data.get("close", 0))

        if entry_price is None or entry_price <= 0:
            return False

        loss_pct = (float(entry_price) - float(current_price)) / float(entry_price)
        return bool(loss_pct >= self.parameters.stop_loss_pct)

    def _check_take_profit(self, data: pd.Series, position: Any) -> bool:
        """Check if take profit is triggered.

        Args:
            data: Current bar data
            position: Current position

        Returns:
            True if take profit is triggered
        """
        entry_price = getattr(position, "entry_price", None)
        current_price = data.get("high", data.get("close", 0))

        if entry_price is None or entry_price <= 0:
            return False

        profit_pct = (float(current_price) - float(entry_price)) / float(entry_price)
        return bool(profit_pct >= self.parameters.take_profit_pct)

    def _check_trailing_stop(self, data: pd.Series, position: Any) -> bool:
        """Check if trailing stop is triggered.

        Args:
            data: Current bar data
            position: Current position

        Returns:
            True if trailing stop is triggered
        """
        symbol = getattr(position, "symbol", None)
        if symbol is None or symbol not in self._highest_price_seen:
            return False

        trailing_pct = self.parameters.trailing_stop_pct
        if trailing_pct is None:
            return False

        highest = self._highest_price_seen[symbol]
        current_price = data.get("low", data.get("close", 0))

        pullback_pct = (float(highest) - float(current_price)) / float(highest)
        return bool(pullback_pct >= trailing_pct)

    def _get_exit_price(self, data: pd.Series) -> Optional[float]:
        """Get exit price for sell order.

        Args:
            data: Current bar data

        Returns:
            Exit price or None
        """
        price = data.get("close")
        return float(price) if price is not None else None

    def _get_signal_indicators(self, data: pd.Series) -> Dict[str, Any]:
        """Get indicator values for signal metadata.

        Args:
            data: Current bar data

        Returns:
            Dictionary of indicator values
        """
        return {
            "close": data.get("close"),
            "high": data.get("high"),
            "low": data.get("low"),
            "volume": data.get("volume"),
        }

    def on_trade(self, trade: Any, context: StrategyContext) -> None:
        """Called when a trade is completed.

        Args:
            trade: Completed trade record
            context: Strategy context
        """
        # Track completed trades
        if hasattr(trade, "symbol"):
            self._positions_exited.append({
                "symbol": trade.symbol,
                "entry_time": getattr(trade, "entry_time", None),
                "exit_time": getattr(trade, "exit_time", None),
                "pnl": getattr(trade, "pnl", 0),
            })
            # Clear trailing stop tracking
            if trade.symbol in self._highest_price_seen:
                del self._highest_price_seen[trade.symbol]

    def get_sell_stats(self) -> Dict[str, Any]:
        """Get sell strategy statistics.

        Returns:
            Dictionary of sell statistics
        """
        total_pnl = sum(p["pnl"] for p in self._positions_exited)
        winning_exits = [p for p in self._positions_exited if p["pnl"] > 0]

        return {
            "sell_signals_generated": self._sell_signals_generated,
            "positions_exited": len(self._positions_exited),
            "winning_exits": len(winning_exits),
            "total_pnl": total_pnl,
            "avg_exit_pnl": total_pnl / len(self._positions_exited) if self._positions_exited else 0.0,
            "win_rate": len(winning_exits) / len(self._positions_exited) if self._positions_exited else 0.0,
        }

    def reset(self) -> None:
        """Reset sell strategy state."""
        super().reset()
        self._sell_signals_generated = 0
        self._positions_exited.clear()
        self._highest_price_seen.clear()
