"""
PnL Calculator Module
Handles PnL calculations for trades and positions
"""

from datetime import datetime
from typing import Optional, Tuple
from hyperliquid_monitor_plus.core_types import Trade
from hyperliquid_monitor_plus.pnl.types import (
    Position, PnLRecord, PositionSide
)


class PnLCalculator:
    """
    Calculator for PnL (Profit and Loss) computations
    
    Handles:
    - Average entry price calculation
    - Realized PnL on position close
    - Unrealized PnL with current price
    - Position updates from trades
    """
    
    @staticmethod
    def calculate_new_avg_price(
        current_size: float,
        current_avg_price: float,
        new_size: float,
        new_price: float
    ) -> float:
        """
        Calculate new weighted average entry price when adding to position
        
        Args:
            current_size: Current position size
            current_avg_price: Current average entry price
            new_size: Size of new trade
            new_price: Price of new trade
            
        Returns:
            New weighted average entry price
        """
        if current_size + new_size == 0:
            return 0.0
        
        total_cost = (current_size * current_avg_price) + (new_size * new_price)
        new_avg_price = total_cost / (current_size + new_size)
        
        return new_avg_price
    
    @staticmethod
    def calculate_realized_pnl(
        position_side: PositionSide,
        entry_price: float,
        exit_price: float,
        size: float
    ) -> float:
        """
        Calculate realized PnL when closing (part of) a position
        
        Args:
            position_side: Whether position was LONG or SHORT
            entry_price: Average entry price
            exit_price: Exit/close price
            size: Size being closed
            
        Returns:
            Realized PnL in USD
        """
        if position_side == "LONG":
            # Long position profits when price goes up
            pnl = (exit_price - entry_price) * size
        elif position_side == "SHORT":
            # Short position profits when price goes down
            pnl = (entry_price - exit_price) * size
        else:
            pnl = 0.0
        
        return pnl
    
    @staticmethod
    def calculate_unrealized_pnl(
        position: Position,
        current_price: float
    ) -> float:
        """
        Calculate unrealized PnL for an open position
        
        Args:
            position: Current position
            current_price: Current market price
            
        Returns:
            Unrealized PnL in USD
        """
        if not position.is_open():
            return 0.0
        
        return PnLCalculator.calculate_realized_pnl(
            position.side,
            position.avg_entry_price,
            current_price,
            position.size
        )
    
    @staticmethod
    def update_position_from_trade(
        position: Position,
        trade: Trade
    ) -> Tuple[Position, Optional[PnLRecord]]:
        """
        Update position based on a new trade and calculate any realized PnL
        
        This method handles:
        - Opening new positions
        - Adding to existing positions
        - Partially closing positions
        - Fully closing positions
        - Reversing positions (close + open opposite)
        
        Args:
            position: Current position for the coin
            trade: New trade to process
            
        Returns:
            Tuple of (updated_position, pnl_record if any)
        """
        pnl_record = None
        
        # Determine trade direction relative to position
        is_buy = trade.side == "BUY"
        
        # Current position state
        current_size = position.size
        current_avg = position.avg_entry_price
        current_side = position.side
        
        # Trade details
        trade_size = trade.size
        trade_price = trade.price
        trade_fee = trade.fee or 0.0
        
        # Case 1: No existing position - open new
        if current_side == "NEUTRAL" or current_size == 0:
            new_side: PositionSide = "LONG" if is_buy else "SHORT"
            position.side = new_side
            position.size = trade_size
            position.avg_entry_price = trade_price
            position.total_cost = trade_size * trade_price
            position.trades_count += 1
            position.last_updated = trade.timestamp
            
        # Case 2: Adding to existing position (same direction)
        elif (current_side == "LONG" and is_buy) or (current_side == "SHORT" and not is_buy):
            new_avg = PnLCalculator.calculate_new_avg_price(
                current_size, current_avg, trade_size, trade_price
            )
            position.size = current_size + trade_size
            position.avg_entry_price = new_avg
            position.total_cost = position.size * new_avg
            position.trades_count += 1
            position.last_updated = trade.timestamp
            
        # Case 3: Reducing/closing position (opposite direction)
        else:
            # Calculate realized PnL
            close_size = min(trade_size, current_size)
            realized_pnl = PnLCalculator.calculate_realized_pnl(
                current_side, current_avg, trade_price, close_size
            )
            
            # Use closed_pnl from trade if available (more accurate)
            if trade.closed_pnl is not None and trade.closed_pnl != 0:
                realized_pnl = trade.closed_pnl
            
            is_full_close = trade_size >= current_size
            
            # Create PnL record
            pnl_record = PnLRecord(
                timestamp=trade.timestamp,
                coin=trade.coin,
                pnl=realized_pnl,
                trade_side=trade.side,
                entry_price=current_avg,
                exit_price=trade_price,
                size=close_size,
                fee=trade_fee,
                is_full_close=is_full_close
            )
            
            # Update position
            remaining_size = current_size - trade_size
            
            if remaining_size <= 0:
                # Position fully closed or reversed
                if remaining_size < 0:
                    # Position reversed - open opposite direction
                    new_side = "LONG" if is_buy else "SHORT"
                    position.side = new_side
                    position.size = abs(remaining_size)
                    position.avg_entry_price = trade_price
                    position.total_cost = position.size * trade_price
                else:
                    # Position fully closed
                    position.side = "NEUTRAL"
                    position.size = 0.0
                    position.avg_entry_price = 0.0
                    position.total_cost = 0.0
            else:
                # Position partially closed
                position.size = remaining_size
                # Average entry price stays the same for remaining position
                position.total_cost = position.size * position.avg_entry_price
            
            position.trades_count += 1
            position.last_updated = trade.timestamp
        
        return position, pnl_record
    
    @staticmethod
    def calculate_pnl_percentage(
        pnl: float,
        entry_value: float
    ) -> float:
        """
        Calculate PnL as a percentage
        
        Args:
            pnl: Profit/loss amount
            entry_value: Entry value (entry_price * size)
            
        Returns:
            PnL percentage
        """
        if entry_value == 0:
            return 0.0
        return (pnl / entry_value) * 100
    
    @staticmethod
    def calculate_roi(
        pnl: float,
        total_invested: float
    ) -> float:
        """
        Calculate Return on Investment
        
        Args:
            pnl: Total profit/loss
            total_invested: Total amount invested
            
        Returns:
            ROI as percentage
        """
        if total_invested == 0:
            return 0.0
        return (pnl / total_invested) * 100
