"""
PnL Manager Module
Central management for PnL tracking and analysis
"""

import threading
from datetime import datetime
from typing import Dict, List, Optional, Callable, Any
from hyperliquid_monitor_plus.core_types import Trade
from hyperliquid_monitor_plus.pnl.types import (
    Position, PnLRecord, CoinPnL, PortfolioPnL
)
from hyperliquid_monitor_plus.pnl.calculator import PnLCalculator


# Type for PnL callbacks
PnLCallback = Callable[[PnLRecord], None]


class PnLManager:
    """
    Central manager for PnL tracking and analysis
    
    Features:
    - Track positions per coin
    - Calculate realized PnL on trades
    - Calculate unrealized PnL with current prices
    - Aggregate portfolio statistics
    - Thread-safe operations
    - Callbacks for PnL events
    """
    
    def __init__(
        self,
        pnl_callback: Optional[PnLCallback] = None,
        address: Optional[str] = None
    ):
        """
        Initialize PnL Manager
        
        Args:
            pnl_callback: Optional callback for PnL events
            address: Optional address to filter trades (if None, process all)
        """
        self._positions: Dict[str, Position] = {}
        self._coin_pnls: Dict[str, CoinPnL] = {}
        self._portfolio = PortfolioPnL()
        self._pnl_callback = pnl_callback
        self._address = address.lower() if address else None
        self._lock = threading.RLock()  # Use RLock to allow reentrant locking
        self._current_prices: Dict[str, float] = {}
    
    def process_trade(self, trade: Trade) -> Optional[PnLRecord]:
        """
        Process a trade and update PnL tracking
        
        Args:
            trade: Trade to process
            
        Returns:
            PnLRecord if trade resulted in realized PnL, None otherwise
        """
        # Filter by address if specified
        if self._address and trade.address.lower() != self._address:
            return None
        
        with self._lock:
            coin = trade.coin
            
            # Initialize coin data if needed
            if coin not in self._positions:
                self._positions[coin] = Position(coin=coin)
            if coin not in self._coin_pnls:
                self._coin_pnls[coin] = CoinPnL(coin=coin)
            
            # Get current position and coin PnL
            position = self._positions[coin]
            coin_pnl = self._coin_pnls[coin]
            
            # Update position and get any realized PnL
            updated_position, pnl_record = PnLCalculator.update_position_from_trade(
                position, trade
            )
            
            # Store updated position
            self._positions[coin] = updated_position
            coin_pnl.current_position = updated_position
            
            # Update coin statistics
            coin_pnl.trades_count += 1
            coin_pnl.total_volume += trade.size * trade.price
            coin_pnl.total_fees += trade.fee or 0.0
            
            # Process realized PnL
            if pnl_record:
                coin_pnl.realized_pnl += pnl_record.pnl
                coin_pnl.pnl_history.append(pnl_record)
                
                # Update win/loss counts
                if pnl_record.pnl > 0:
                    coin_pnl.winning_trades += 1
                elif pnl_record.pnl < 0:
                    coin_pnl.losing_trades += 1
                
                # Update portfolio totals
                self._portfolio.total_realized_pnl += pnl_record.pnl
                
                # Trigger callback
                if self._pnl_callback:
                    try:
                        self._pnl_callback(pnl_record)
                    except Exception:
                        pass  # Don't let callback errors break the manager
            
            # Update portfolio (fees counted for all trades)
            self._portfolio.total_fees += trade.fee or 0.0
            self._portfolio.total_volume += trade.size * trade.price
            self._portfolio.coin_pnls = self._coin_pnls.copy()
            self._portfolio.last_updated = datetime.now()
            
            # Update current price for this coin
            self._current_prices[coin] = trade.price
            
            return pnl_record
    
    def update_prices(self, prices: Dict[str, float]) -> None:
        """
        Update current prices for unrealized PnL calculation
        
        Args:
            prices: Dictionary of coin -> current_price
        """
        with self._lock:
            self._current_prices.update(prices)
            self._recalculate_unrealized_pnl()
    
    def update_price(self, coin: str, price: float) -> None:
        """
        Update price for a single coin
        
        Args:
            coin: Coin symbol
            price: Current price
        """
        with self._lock:
            self._current_prices[coin] = price
            self._recalculate_unrealized_pnl()
    
    def _recalculate_unrealized_pnl(self) -> None:
        """Recalculate all unrealized PnL with current prices"""
        total_unrealized = 0.0
        
        for coin, position in self._positions.items():
            if position.is_open() and coin in self._current_prices:
                unrealized = PnLCalculator.calculate_unrealized_pnl(
                    position, self._current_prices[coin]
                )
                
                if coin in self._coin_pnls:
                    self._coin_pnls[coin].unrealized_pnl = unrealized
                
                total_unrealized += unrealized
        
        self._portfolio.total_unrealized_pnl = total_unrealized
    
    def get_position(self, coin: str) -> Optional[Position]:
        """
        Get current position for a coin
        
        Args:
            coin: Coin symbol
            
        Returns:
            Position or None if not found
        """
        with self._lock:
            return self._positions.get(coin)
    
    def get_all_positions(self) -> Dict[str, Position]:
        """
        Get all current positions
        
        Returns:
            Dictionary of coin -> Position
        """
        with self._lock:
            return self._positions.copy()
    
    def get_open_positions(self) -> Dict[str, Position]:
        """
        Get only open positions
        
        Returns:
            Dictionary of coin -> Position for open positions
        """
        with self._lock:
            return {
                coin: pos for coin, pos in self._positions.items()
                if pos.is_open()
            }
    
    def get_coin_pnl(self, coin: str) -> Optional[CoinPnL]:
        """
        Get PnL statistics for a coin
        
        Args:
            coin: Coin symbol
            
        Returns:
            CoinPnL or None if not found
        """
        with self._lock:
            return self._coin_pnls.get(coin)
    
    def get_portfolio_pnl(self) -> PortfolioPnL:
        """
        Get overall portfolio PnL
        
        Returns:
            PortfolioPnL with all statistics
        """
        with self._lock:
            # Make sure coin_pnls is up to date
            self._portfolio.coin_pnls = self._coin_pnls.copy()
            return self._portfolio
    
    def get_pnl_history(self, coin: Optional[str] = None) -> List[PnLRecord]:
        """
        Get PnL history
        
        Args:
            coin: Optional coin to filter by
            
        Returns:
            List of PnLRecords
        """
        with self._lock:
            if coin:
                if coin in self._coin_pnls:
                    return self._coin_pnls[coin].pnl_history.copy()
                return []
            
            # Return all PnL history across all coins
            all_history = []
            for coin_pnl in self._coin_pnls.values():
                all_history.extend(coin_pnl.pnl_history)
            
            # Sort by timestamp
            all_history.sort(key=lambda x: x.timestamp)
            return all_history
    
    def get_statistics(self) -> Dict[str, Any]:
        """
        Get comprehensive statistics
        
        Returns:
            Dictionary with all statistics
        """
        with self._lock:
            portfolio = self.get_portfolio_pnl()
            open_positions = self.get_open_positions()
            
            # Calculate win rate
            total_wins = sum(cp.winning_trades for cp in self._coin_pnls.values())
            total_losses = sum(cp.losing_trades for cp in self._coin_pnls.values())
            total_closed = total_wins + total_losses
            win_rate = (total_wins / total_closed * 100) if total_closed > 0 else 0.0
            
            return {
                "total_realized_pnl": portfolio.total_realized_pnl,
                "total_unrealized_pnl": portfolio.total_unrealized_pnl,
                "total_pnl": portfolio.total_pnl,
                "net_pnl": portfolio.net_pnl,
                "total_fees": portfolio.total_fees,
                "total_volume": portfolio.total_volume,
                "total_trades": portfolio.total_trades,
                "open_positions_count": len(open_positions),
                "coins_traded": len(self._coin_pnls),
                "winning_trades": total_wins,
                "losing_trades": total_losses,
                "win_rate": win_rate,
                "best_performer": portfolio.get_best_performer(),
                "worst_performer": portfolio.get_worst_performer(),
                "open_positions": {
                    coin: {
                        "side": pos.side,
                        "size": pos.size,
                        "avg_entry": pos.avg_entry_price,
                        "unrealized_pnl": self._coin_pnls[coin].unrealized_pnl
                        if coin in self._coin_pnls else 0.0
                    }
                    for coin, pos in open_positions.items()
                }
            }
    
    def reset(self) -> None:
        """Reset all PnL tracking data"""
        with self._lock:
            self._positions.clear()
            self._coin_pnls.clear()
            self._portfolio = PortfolioPnL()
            self._current_prices.clear()
    
    def get_summary_report(self) -> str:
        """
        Generate a text summary report of PnL
        
        Returns:
            Formatted string report
        """
        stats = self.get_statistics()
        
        report_lines = [
            "=" * 50,
            "PnL SUMMARY REPORT",
            "=" * 50,
            "",
            "📊 OVERALL PERFORMANCE",
            f"  Total Realized PnL: ${stats['total_realized_pnl']:,.2f}",
            f"  Total Unrealized PnL: ${stats['total_unrealized_pnl']:,.2f}",
            f"  Total PnL: ${stats['total_pnl']:,.2f}",
            f"  Net PnL (after fees): ${stats['net_pnl']:,.2f}",
            "",
            "📈 TRADING STATISTICS",
            f"  Total Trades: {stats['total_trades']}",
            f"  Total Volume: ${stats['total_volume']:,.2f}",
            f"  Total Fees: ${stats['total_fees']:,.2f}",
            f"  Win Rate: {stats['win_rate']:.1f}%",
            f"  Winning Trades: {stats['winning_trades']}",
            f"  Losing Trades: {stats['losing_trades']}",
            "",
            "🎯 POSITIONS",
            f"  Coins Traded: {stats['coins_traded']}",
            f"  Open Positions: {stats['open_positions_count']}",
        ]
        
        if stats['best_performer']:
            report_lines.append(f"  Best Performer: {stats['best_performer']}")
        if stats['worst_performer']:
            report_lines.append(f"  Worst Performer: {stats['worst_performer']}")
        
        if stats['open_positions']:
            report_lines.append("")
            report_lines.append("📍 OPEN POSITIONS DETAIL:")
            for coin, pos_info in stats['open_positions'].items():
                report_lines.append(
                    f"  {coin}: {pos_info['side']} {pos_info['size']:.4f} @ "
                    f"${pos_info['avg_entry']:.2f} "
                    f"(Unrealized: ${pos_info['unrealized_pnl']:,.2f})"
                )
        
        report_lines.extend(["", "=" * 50])
        
        return "\n".join(report_lines)
