"""
Market Impact Analyzer - Advanced market impact calculation and prediction

This module provides sophisticated market impact analysis for whale trades,
including slippage prediction, depth analysis, and optimal following strategies.
"""

import logging
from typing import Dict, List, Optional, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass, field

from ..core_types import (
    EnhancedTrade, WhaleBotConfig, WhaleTier, FollowRecommendation, FollowAction
)
from ..core_types import MarketImpactAnalysis as MarketImpact

logger = logging.getLogger(__name__)

@dataclass
class ImpactCalculation:
    """Detailed impact calculation results"""
    predicted_price_impact: float  # % price movement
    slippage_estimate: float  # % slippage
    market_depth_at_trade: Dict[str, float]  # Available liquidity levels
    optimal_follow_size: Optional[float] = None
    entry_timing_window: Optional[int] = None  # Seconds
    risk_score: float = 0.0  # 0.0 to 1.0 risk assessment
    calculation_confidence: float = 0.0  # 0.0 to 1.0 confidence

@dataclass
class LiquidityProfile:
    """Market liquidity profile for a coin"""
    coin: str
    last_updated: datetime
    avg_daily_volume: float  # USD
    liquidity_levels: Dict[float, float]  # price -> available volume
    bid_ask_spread: float  # %
    volatility_24h: float  # %
    
    def get_liquidity_at_price(self, price: float, tolerance: float = 0.001) -> float:
        """Get available liquidity near a specific price"""
        closest_prices = sorted(
            (abs(p - price), vol) for p, vol in self.liquidity_levels.items()
        )
        if closest_prices:
            return closest_prices[0][1]  # Return volume for closest price
        return 0.0

class MarketImpactAnalyzer:
    """
    Advanced market impact analyzer for whale trades.
    
    This analyzer provides:
    - Real-time market impact prediction
    - Liquidity depth analysis  
    - Slippage estimation
    - Optimal following size calculation
    - Risk assessment
    """
    
    def __init__(self, config: WhaleBotConfig):
        """
        Initialize the market impact analyzer.
        
        Args:
            config: Whale bot configuration with impact thresholds
        """
        self.config = config
        self.liquidity_profiles: Dict[str, LiquidityProfile] = {}
        self.trade_impact_history: List[Tuple[EnhancedTrade, ImpactCalculation]] = []
        self.coin_stats: Dict[str, Dict] = {}  # Historical statistics per coin
        self._logger = logger
        
        # Impact calculation parameters
        self.default_liquidity_profile = LiquidityProfile(
            coin="UNKNOWN",
            last_updated=datetime.now(),
            avg_daily_volume=1_000_000,  # $1M default
            liquidity_levels={0.0: 100_000, 1.0: 500_000, 2.0: 1_000_000},  # Default levels
            bid_ask_spread=0.001,  # 0.1% default spread
            volatility_24h=0.02  # 2% default volatility
        )
    
    def analyze_market_impact(self, trade: EnhancedTrade) -> ImpactCalculation:
        """
        Analyze market impact for a given whale trade.
        
        Args:
            trade: Enhanced trade to analyze
            
        Returns:
            ImpactCalculation: Detailed impact analysis
        """
        try:
            self._logger.debug(f"Analyzing market impact for ${trade.size_usd:,.0f} {trade.coin} trade")
            
            # Get or create liquidity profile
            liquidity = self._get_liquidity_profile(trade.coin)
            
            # Calculate price impact
            price_impact = self._calculate_price_impact(trade, liquidity)
            
            # Calculate slippage estimate
            slippage = self._calculate_slippage(trade, liquidity, price_impact)
            
            # Analyze market depth
            depth_analysis = self._analyze_depth(trade, liquidity)
            
            # Calculate optimal follow size
            optimal_size = self._calculate_optimal_follow_size(trade, liquidity, price_impact)
            
            # Calculate entry timing window
            timing_window = self._calculate_timing_window(trade, price_impact)
            
            # Assess risk
            risk_score = self._assess_risk(trade, price_impact, slippage)
            
            # Calculate confidence based on data quality
            confidence = self._calculate_confidence(trade, liquidity)
            
            calculation = ImpactCalculation(
                predicted_price_impact=price_impact,
                slippage_estimate=slippage,
                market_depth_at_trade=depth_analysis,
                optimal_follow_size=optimal_size,
                entry_timing_window=timing_window,
                risk_score=risk_score,
                calculation_confidence=confidence
            )
            
            # Store for historical analysis
            self.trade_impact_history.append((trade, calculation))
            
            # Keep history manageable
            if len(self.trade_impact_history) > 1000:
                self.trade_impact_history = self.trade_impact_history[-1000:]
            
            self._logger.debug(f"Market impact analysis: {price_impact:.3f}% impact, "
                              f"{slippage:.3f}% slippage, risk: {risk_score:.3f}")
            
            return calculation
            
        except Exception as e:
            self._logger.error(f"Error analyzing market impact: {e}")
            return self._get_default_calculation()
    
    def _get_liquidity_profile(self, coin: str) -> LiquidityProfile:
        """Get liquidity profile for a coin, create default if not available"""
        if coin in self.liquidity_profiles:
            # Update timestamp to indicate recent usage
            self.liquidity_profiles[coin].last_updated = datetime.now()
            return self.liquidity_profiles[coin]
        else:
            # Create default profile for new coin
            default_profile = LiquidityProfile(
                coin=coin,
                last_updated=datetime.now(),
                avg_daily_volume=self._estimate_daily_volume(coin),
                liquidity_levels=self._create_default_levels(),
                bid_ask_spread=self._estimate_spread(coin),
                volatility_24h=self._estimate_volatility(coin)
            )
            
            self.liquidity_profiles[coin] = default_profile
            self._logger.debug(f"Created default liquidity profile for {coin}")
            return default_profile
    
    def _calculate_price_impact(self, trade: EnhancedTrade, liquidity: LiquidityProfile) -> float:
        """
        Calculate predicted price impact using market depth analysis.
        
        Uses a simplified version of market impact models:
        - Square-root model for large trades
        - Linear model for smaller trades
        """
        try:
            # Get trade size relative to market
            trade_size_ratio = trade.size_usd / liquidity.avg_daily_volume
            
            if trade_size_ratio <= 0.01:  # Less than 1% of daily volume
                # Linear impact model
                impact = trade_size_ratio * 0.1  # 10% impact coefficient
            elif trade_size_ratio <= 0.05:  # 1-5% of daily volume
                # Transition to square-root model
                linear_part = 0.01 * 0.1
                sqrt_part = (trade_size_ratio - 0.01) * 0.05  # 20% coefficient
                impact = linear_part + sqrt_part
            else:  # Greater than 5% of daily volume
                # Square-root model for large trades
                impact = (trade_size_ratio ** 0.5) * 0.03  # 3% coefficient
            
            # Adjust for tier - bigger whales cause proportionally less impact
            tier_multipliers = {
                WhaleTier.MINNOW: 1.5,
                WhaleTier.DOLPHIN: 1.2,
                WhaleTier.WHALE: 1.0,
                WhaleTier.MEGA_WHALE: 0.8,
                WhaleTier.GIANT_WHALE: 0.6
            }
            
            tier_multiplier = tier_multipliers.get(trade.whale_tier, 1.0)
            adjusted_impact = impact * tier_multiplier
            
            # Apply bounds
            return max(0.0001, min(adjusted_impact, 0.1))  # 0.01% to 10%
            
        except Exception as e:
            self._logger.debug(f"Error calculating price impact: {e}")
            return 0.001  # Default 0.1%
    
    def _calculate_slippage(self, trade: EnhancedTrade, liquidity: LiquidityProfile, 
                           price_impact: float) -> float:
        """
        Calculate estimated slippage for a trade of this size.
        
        Slippage is typically higher than price impact and includes:
        - Market depth consumption
        - Spread widening  
        - Adverse selection
        """
        try:
            # Base slippage from price impact
            base_slippage = price_impact * 1.5  # Slippage is typically 1.5x impact
            
            # Adjust for spread
            spread_adjustment = 1 + liquidity.bid_ask_spread
            
            # Adjust for volatility
            volatility_adjustment = 1 + (liquidity.volatility_24h * 2)
            
            # Size-based adjustment - larger trades have proportionally less slippage
            size_factor = min(1.0, 0.7 + (trade.size_usd / 10_000_000) * 0.3)
            
            total_slippage = base_slippage * spread_adjustment * volatility_adjustment * size_factor
            
            # Apply bounds and minimum
            return max(0.0001, min(total_slippage, 0.15))  # 0.01% to 15%
            
        except Exception as e:
            self._logger.debug(f"Error calculating slippage: {e}")
            return 0.001  # Default 0.1%
    
    def _analyze_depth(self, trade: EnhancedTrade, liquidity: LiquidityProfile) -> Dict[str, float]:
        """Analyze market depth at different price levels"""
        try:
            # Simulate depth consumption at current price
            remaining_volume = trade.size_usd
            depth_levels = {}
            
            # Sort liquidity levels by price
            sorted_levels = sorted(liquidity.liquidity_levels.items())
            
            for price_level, available_volume in sorted_levels:
                if remaining_volume <= 0:
                    break
                    
                consumed = min(remaining_volume, available_volume)
                depth_levels[f"price_{price_level:.4f}"] = consumed
                remaining_volume -= consumed
            
            # Add summary statistics
            depth_levels["total_depth_consumed"] = trade.size_usd - remaining_volume
            depth_levels["depth_remaining_ratio"] = remaining_volume / trade.size_usd
            depth_levels["avg_price_paid"] = self._calculate_avg_price(trade, depth_levels)
            
            return depth_levels
            
        except Exception as e:
            self._logger.debug(f"Error analyzing depth: {e}")
            return {"error": "depth_analysis_failed"}
    
    def _calculate_optimal_follow_size(self, trade: EnhancedTrade, liquidity: LiquidityProfile,
                                     price_impact: float) -> Optional[float]:
        """
        Calculate optimal position size for following this whale trade.
        
        Considers:
        - Available liquidity
        - Risk tolerance
        - Expected profit after slippage
        - Portfolio allocation limits
        """
        try:
            # Base follow size from configuration
            base_size = trade.size_usd * self.config.follow_ratio
            
            # Adjust based on liquidity
            liquidity_availability = self._get_liquidity_availability(liquidity)
            liquidity_adjusted = base_size * liquidity_availability
            
            # Adjust based on market impact (avoid following if impact too high)
            if price_impact > self.config.max_market_impact_threshold:
                return None  # Don't follow if impact too high
                
            # Adjust based on slippage
            if trade.slippage and trade.slippage > self.config.max_slippage_threshold:
                liquidity_adjusted *= 0.5  # Reduce follow size if high slippage
            
            # Apply tier-based limits
            tier_limits = {
                WhaleTier.MINNOW: 0.5,   # Don't follow minnows much
                WhaleTier.DOLPHIN: 0.7,
                WhaleTier.WHALE: 1.0,    # Full follow ratio
                WhaleTier.MEGA_WHALE: 1.2,  # Can follow slightly more
                WhaleTier.GIANT_WHALE: 1.5   # Can follow significantly more
            }
            
            tier_limit = tier_limits.get(trade.whale_tier, 1.0)
            final_size = liquidity_adjusted * tier_limit
            
            # Apply maximum limits
            max_follow_size = trade.size_usd * self.config.max_follow_ratio
            final_size = min(final_size, max_follow_size)
            
            # Ensure minimum viable size
            min_viable_size = 10_000  # $10K minimum
            if final_size < min_viable_size:
                return None  # Not worth following if too small
                
            return final_size
            
        except Exception as e:
            self._logger.debug(f"Error calculating optimal follow size: {e}")
            return None
    
    def _calculate_timing_window(self, trade: EnhancedTrade, price_impact: float) -> int:
        """
        Calculate optimal entry timing window in seconds.
        
        Shorter window for:
        - Higher price impact (price moving fast)
        - Larger trades
        - More urgent trades
        """
        try:
            # Base timing window
            base_window = 30  # 30 seconds
            
            # Adjust for price impact
            impact_factor = max(0.3, 1.0 - (price_impact * 10))  # Higher impact = shorter window
            
            # Adjust for trade size
            size_factor = max(0.5, 1.0 - (trade.size_usd / 50_000_000))  # Larger trades = shorter window
            
            # Adjust for urgency (if available)
            urgency_factor = 1.0  # Default
            # Note: Would need to pass urgency from trade or use classification
            
            calculated_window = base_window * impact_factor * size_factor * urgency_factor
            
            # Apply bounds
            return max(5, min(int(calculated_window), 120))  # 5 seconds to 2 minutes
            
        except Exception as e:
            self._logger.debug(f"Error calculating timing window: {e}")
            return 30  # Default 30 seconds
    
    def _assess_risk(self, trade: EnhancedTrade, price_impact: float, slippage: float) -> float:
        """Assess risk score for following this trade (0.0 to 1.0)"""
        try:
            risk_factors = []
            
            # Market impact risk
            impact_risk = min(price_impact / 0.01, 1.0)  # 1% impact = max risk
            risk_factors.append(impact_risk * 0.3)
            
            # Slippage risk
            slippage_risk = min(slippage / 0.005, 1.0)  # 0.5% slippage = max risk
            risk_factors.append(slippage_risk * 0.3)
            
            # Size risk (larger trades riskier)
            size_risk = min(trade.size_usd / 20_000_000, 1.0)  # $20M = max risk
            risk_factors.append(size_risk * 0.2)
            
            # Volatility risk (simplified)
            volatility_risk = min(trade.size_usd / 100_000, 0.5)  # Cap volatility risk
            risk_factors.append(volatility_risk * 0.2)
            
            total_risk = sum(risk_factors)
            return min(total_risk, 1.0)
            
        except Exception as e:
            self._logger.debug(f"Error assessing risk: {e}")
            return 0.5  # Default medium risk
    
    def _calculate_confidence(self, trade: EnhancedTrade, liquidity: LiquidityProfile) -> float:
        """Calculate confidence in the impact calculation (0.0 to 1.0)"""
        try:
            confidence_factors = []
            
            # Data freshness
            age_minutes = (datetime.now() - liquidity.last_updated).total_seconds() / 60
            freshness_score = max(0.0, 1.0 - (age_minutes / 60))  # 1 hour = 0 confidence
            confidence_factors.append(freshness_score * 0.3)
            
            # Historical data availability
            if trade.coin in self.coin_stats:
                stats = self.coin_stats[trade.coin]
                data_score = min(stats.get('trade_count', 0) / 100, 1.0)  # 100 trades = full confidence
                confidence_factors.append(data_score * 0.4)
            else:
                confidence_factors.append(0.2)  # Low confidence for unknown coins
            
            # Liquidity profile completeness
            profile_completeness = len(liquidity.liquidity_levels) / 10  # 10 levels = full confidence
            confidence_factors.append(min(profile_completeness, 1.0) * 0.3)
            
            return sum(confidence_factors)
            
        except Exception as e:
            self._logger.debug(f"Error calculating confidence: {e}")
            return 0.5  # Default medium confidence
    
    def _get_liquidity_availability(self, liquidity: LiquidityProfile) -> float:
        """Calculate liquidity availability factor (0.0 to 1.0)"""
        try:
            # Simplified liquidity assessment
            total_liquidity = sum(liquidity.liquidity_levels.values())
            
            # Compare to minimum viable liquidity
            min_liquidity = 100_000  # $100K minimum
            
            if total_liquidity < min_liquidity:
                return 0.1  # Very low liquidity
            elif total_liquidity < 500_000:  # $500K
                return 0.5
            elif total_liquidity < 1_000_000:  # $1M
                return 0.8
            else:
                return 1.0
                
        except Exception as e:
            self._logger.debug(f"Error calculating liquidity availability: {e}")
            return 0.5
    
    def _calculate_avg_price(self, trade: EnhancedTrade, depth_levels: Dict[str, float]) -> float:
        """Calculate average price paid based on depth consumption"""
        try:
            total_consumed = depth_levels.get("total_depth_consumed", 0)
            if total_consumed <= 0:
                return trade.price
            
            # Simplified average price calculation
            # In reality, this would be more complex
            return trade.price * (1 + depth_levels.get("depth_remaining_ratio", 0) * 0.5)
            
        except Exception as e:
            self._logger.debug(f"Error calculating average price: {e}")
            return trade.price
    
    # Placeholder methods for real market data integration
    def _estimate_daily_volume(self, coin: str) -> float:
        """Estimate daily volume for a coin"""
        # This would integrate with real market data APIs
        return 1_000_000  # Default $1M
    
    def _create_default_levels(self) -> Dict[float, float]:
        """Create default liquidity levels"""
        return {
            0.995: 50_000,
            0.998: 100_000,
            1.000: 200_000,  # Current price
            1.002: 150_000,
            1.005: 100_000
        }
    
    def _estimate_spread(self, coin: str) -> float:
        """Estimate bid-ask spread for a coin"""
        # This would use real market data
        return 0.001  # 0.1%
    
    def _estimate_volatility(self, coin: str) -> float:
        """Estimate 24-hour volatility for a coin"""
        # This would use real market data
        return 0.02  # 2%
    
    def _get_default_calculation(self) -> ImpactCalculation:
        """Return default calculation when real calculation fails"""
        return ImpactCalculation(
            predicted_price_impact=0.001,  # 0.1%
            slippage_estimate=0.0015,      # 0.15%
            market_depth_at_trade={},
            optimal_follow_size=None,
            entry_timing_window=30,
            risk_score=0.5,
            calculation_confidence=0.3
        )
    
    def update_liquidity_profile(self, coin: str, profile_data: Dict):
        """Update liquidity profile with real market data"""
        try:
            # This would integrate with real-time market data
            # For now, just update the timestamp
            if coin in self.liquidity_profiles:
                self.liquidity_profiles[coin].last_updated = datetime.now()
                self._logger.debug(f"Updated liquidity profile timestamp for {coin}")
        except Exception as e:
            self._logger.error(f"Error updating liquidity profile: {e}")
    
    def get_impact_summary(self) -> Dict:
        """Get summary of recent impact analyses"""
        try:
            if not self.trade_impact_history:
                return {"message": "No impact analyses completed yet"}
            
            recent_analyses = self.trade_impact_history[-50:]  # Last 50 analyses
            
            avg_impact = sum(calc.predicted_price_impact for _, calc in recent_analyses) / len(recent_analyses)
            avg_slippage = sum(calc.slippage_estimate for _, calc in recent_analyses) / len(recent_analyses)
            avg_risk = sum(calc.risk_score for _, calc in recent_analyses) / len(recent_analyses)
            
            return {
                "analyses_completed": len(self.trade_impact_history),
                "recent_analyses": len(recent_analyses),
                "avg_predicted_impact": f"{avg_impact:.4f}",
                "avg_slippage_estimate": f"{avg_slippage:.4f}",
                "avg_risk_score": f"{avg_risk:.3f}",
                "coins_with_profiles": list(self.liquidity_profiles.keys()),
                "high_impact_trades": sum(1 for _, calc in recent_analyses if calc.predicted_price_impact > 0.005),
                "high_risk_trades": sum(1 for _, calc in recent_analyses if calc.risk_score > 0.7)
            }
            
        except Exception as e:
            self._logger.error(f"Error generating impact summary: {e}")
            return {"error": str(e)}