"""Market Making Strategy - Profits from the bid-ask spread in sideways markets."""

from decimal import Decimal
from typing import Dict, Any, Optional, List
from datetime import datetime, timedelta

from ..base import BaseStrategy, StrategySignal, SignalType
from src.data.pricing_service import get_pricing_service
from src.data.indicators import get_indicators
from src.core.logger import get_logger

logger = get_logger(__name__)


class MarketMakingStrategy(BaseStrategy):
    """
    Market Making Strategy that profits from the bid-ask spread.
    
    Logic:
    1. Calculate mid-price from order book or last trade.
    2. Place limit BUY order at (mid - spread%).
    3. Place limit SELL order at (mid + spread%).
    4. Re-quote orders when price moves beyond threshold.
    5. Use inventory skew to manage risk (if holding too much, lower sell price).
    
    Best suited for:
    - Sideways/ranging markets with low volatility
    - High-liquidity pairs with tight spreads
    
    Risks:
    - Adverse selection (getting filled on wrong side during trends)
    - Inventory accumulation
    """

    def __init__(self, name: str, parameters: Dict[str, Any], timeframes: List[str] = None):
        super().__init__(name, parameters, timeframes)
        
        # Strategy parameters
        self.spread_percent = float(parameters.get('spread_percent', 0.2)) / 100  # Convert to decimal
        self.requote_threshold = float(parameters.get('requote_threshold', 0.5)) / 100
        self.max_inventory = Decimal(str(parameters.get('max_inventory', 1.0)))
        self.inventory_skew_factor = float(parameters.get('inventory_skew_factor', 0.5))
        self.min_adx = float(parameters.get('min_adx', 20))  # Only make markets when ADX < this (low trend)
        self.order_size_percent = float(parameters.get('order_size_percent', 5)) / 100
        
        self.pricing_service = get_pricing_service()
        self.indicators = get_indicators()
        
        # Track active orders and inventory
        self._current_inventory = Decimal('0')
        self._last_mid_price: Optional[Decimal] = None
        self._last_quote_time: Optional[datetime] = None
        self._active_orders: Dict[str, Any] = {}  # side -> order info

    def _calculate_skewed_spread(self, inventory: Decimal) -> tuple[float, float]:
        """Calculate bid/ask spread with inventory skew.
        
        When inventory is positive (long), we want to sell more aggressively.
        When inventory is negative (short), we want to buy more aggressively.
        
        Returns:
            Tuple of (bid_spread, ask_spread) as percentages
        """
        base_spread = self.spread_percent
        
        # Calculate skew based on inventory relative to max
        if self.max_inventory > 0:
            inventory_ratio = float(inventory / self.max_inventory)
        else:
            inventory_ratio = 0.0
        
        # Clamp between -1 and 1
        inventory_ratio = max(-1.0, min(1.0, inventory_ratio))
        
        # Apply skew
        skew = inventory_ratio * self.inventory_skew_factor * base_spread
        
        # If positive inventory, tighten ask (lower sell price), widen bid
        # If negative inventory, tighten bid (higher buy price), widen ask
        bid_spread = base_spread + skew  # Positive inventory -> wider bid
        ask_spread = base_spread - skew  # Positive inventory -> tighter ask
        
        # Ensure spreads are positive
        bid_spread = max(0.0001, bid_spread)
        ask_spread = max(0.0001, ask_spread)
        
        return bid_spread, ask_spread

    async def on_tick(self, symbol: str, price: Decimal, timeframe: str, data: Dict[str, Any]) -> Optional[StrategySignal]:
        """Check for market making opportunities."""
        if not self.enabled:
            return None

        try:
            current_price = price
            
            # Fetch OHLCV for ADX calculation
            ohlcv = self.pricing_service.get_ohlcv(
                symbol=symbol,
                timeframe=timeframe,
                limit=30
            )
            
            if ohlcv and len(ohlcv) >= 14:
                df_data = {
                    'high': [float(c[2]) for c in ohlcv],
                    'low': [float(c[3]) for c in ohlcv],
                    'close': [float(c[4]) for c in ohlcv]
                }
                import pandas as pd
                df = pd.DataFrame(df_data)
                
                adx = self.indicators.adx(df['high'], df['low'], df['close'], period=14)
                current_adx = adx.iloc[-1] if not pd.isna(adx.iloc[-1]) else 0.0
                
                # Only make markets in low-trend environments
                if current_adx > self.min_adx:
                    self.logger.debug(
                        f"Market Making skipped: ADX {current_adx:.1f} > {self.min_adx} (trending market)"
                    )
                    return None
            else:
                current_adx = 0.0
            
            # Check if we need to requote
            should_requote = False
            
            if self._last_mid_price is None:
                should_requote = True
            else:
                price_change = abs(float(current_price - self._last_mid_price) / float(self._last_mid_price))
                if price_change > self.requote_threshold:
                    should_requote = True
                    self.logger.debug(f"Requote triggered: price moved {price_change:.2%}")
            
            if not should_requote:
                return None
            
            # Calculate skewed spreads based on inventory
            bid_spread, ask_spread = self._calculate_skewed_spread(self._current_inventory)
            
            # Calculate quote prices
            bid_price = current_price * Decimal(str(1 - bid_spread))
            ask_price = current_price * Decimal(str(1 + ask_spread))
            
            # Round to 2 decimal places
            bid_price = bid_price.quantize(Decimal('0.01'))
            ask_price = ask_price.quantize(Decimal('0.01'))
            
            self._last_mid_price = current_price
            self._last_quote_time = datetime.now()
            
            self.logger.info(
                f"Market Making {symbol}: Mid={current_price}, "
                f"Bid={bid_price} ({bid_spread:.3%}), Ask={ask_price} ({ask_spread:.3%}), "
                f"Inventory={self._current_inventory}, ADX={current_adx:.1f}"
            )
            
            # Generate signal for placing both limit orders
            # The execution engine will need special handling for this
            signal = StrategySignal(
                signal_type=SignalType.HOLD,  # Special: not BUY or SELL, but both
                symbol=symbol,
                strength=1.0 - (current_adx / 100),  # Higher strength when less trending
                price=current_price,
                metadata={
                    "strategy": "market_making",
                    "order_type": "limit_pair",  # Indicates we want both bid and ask
                    "bid_price": float(bid_price),
                    "ask_price": float(ask_price),
                    "bid_spread": bid_spread,
                    "ask_spread": ask_spread,
                    "inventory": float(self._current_inventory),
                    "adx": float(current_adx),
                    "description": f"Market Making: Bid={bid_price}, Ask={ask_price}"
                }
            )
            return signal

        except Exception as e:
            self.logger.error(f"Error in MarketMakingStrategy: {e}")
            return None
            
        return None

    def update_inventory(self, side: str, quantity: Decimal):
        """Update inventory after a fill.
        
        Args:
            side: 'buy' or 'sell'
            quantity: Filled quantity
        """
        if side == 'buy':
            self._current_inventory += quantity
        elif side == 'sell':
            self._current_inventory -= quantity
        
        self.logger.info(f"Inventory updated: {side} {quantity}, new inventory: {self._current_inventory}")

    def get_inventory(self) -> Decimal:
        """Get current inventory position."""
        return self._current_inventory

    def on_signal(self, signal: StrategySignal) -> Optional[StrategySignal]:
        """Process signal (pass-through)."""
        return signal
