"""
Position Tracker Types Module
Defines data structures for real-time position tracking and monitoring
"""

from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Optional, Dict, Literal
from hyperliquid_monitor_plus.pnl.types import PositionSide


# Event types for position changes
PositionEventType = Literal["OPENED", "CLOSED", "INCREASED", "DECREASED", "LIQUIDATED", "UPDATED"]


@dataclass
class LivePosition:
    """
    Represents a live position fetched from the exchange
    
    Attributes:
        coin: The coin/asset symbol
        side: Position side (LONG/SHORT/NEUTRAL)
        size: Current position size (in contracts)
        entry_price: Average entry price
        mark_price: Current mark price
        liquidation_price: Liquidation price
        unrealized_pnl: Current unrealized PnL
        margin_used: Margin used for this position
        leverage: Current leverage
        max_leverage: Maximum allowed leverage
        funding_rate: Current funding rate
        last_updated: Timestamp of last update
    """
    coin: str
    side: PositionSide = "NEUTRAL"
    size: float = 0.0
    entry_price: float = 0.0
    mark_price: float = 0.0
    liquidation_price: Optional[float] = None
    unrealized_pnl: float = 0.0
    margin_used: float = 0.0
    leverage: float = 1.0
    max_leverage: float = 50.0
    funding_rate: float = 0.0
    last_updated: Optional[datetime] = None
    
    def __post_init__(self):
        """Validate position data"""
        if self.size < 0:
            raise ValueError(f"Position size cannot be negative: {self.size}")
        if self.leverage < 0:
            raise ValueError(f"Leverage cannot be negative: {self.leverage}")
    
    @property
    def notional_value(self) -> float:
        """Calculate notional value of position"""
        return self.size * self.mark_price
    
    @property
    def entry_value(self) -> float:
        """Calculate entry value of position"""
        return self.size * self.entry_price
    
    @property
    def pnl_percentage(self) -> float:
        """Calculate PnL as percentage of entry value"""
        if self.entry_value == 0:
            return 0.0
        return (self.unrealized_pnl / self.entry_value) * 100
    
    @property
    def margin_ratio(self) -> float:
        """Calculate margin ratio (margin_used / notional_value)"""
        if self.notional_value == 0:
            return 0.0
        return (self.margin_used / self.notional_value) * 100
    
    @property
    def distance_to_liquidation(self) -> Optional[float]:
        """Calculate percentage distance to liquidation price"""
        if not self.liquidation_price or self.mark_price == 0:
            return None
        
        if self.side == "LONG":
            return ((self.mark_price - self.liquidation_price) / self.mark_price) * 100
        elif self.side == "SHORT":
            return ((self.liquidation_price - self.mark_price) / self.mark_price) * 100
        return None
    
    def is_open(self) -> bool:
        """Check if position is open"""
        return self.size > 0 and self.side != "NEUTRAL"
    
    def is_at_risk(self, threshold: float = 10.0) -> bool:
        """
        Check if position is at liquidation risk
        
        Args:
            threshold: Percentage threshold for risk warning
            
        Returns:
            True if distance to liquidation is below threshold
        """
        distance = self.distance_to_liquidation
        if distance is None:
            return False
        return distance < threshold


@dataclass
class PositionChange:
    """
    Records a change in position
    
    Attributes:
        timestamp: When the change occurred
        coin: The coin/asset
        event_type: Type of position event
        old_position: Previous position state (None if new)
        new_position: New position state
        size_change: Change in position size
        pnl_change: Change in unrealized PnL
    """
    timestamp: datetime
    coin: str
    event_type: PositionEventType
    old_position: Optional[LivePosition]
    new_position: LivePosition
    size_change: float = 0.0
    pnl_change: float = 0.0
    
    @property
    def is_significant(self) -> bool:
        """Check if this is a significant change (not just price update)"""
        return self.event_type in ("OPENED", "CLOSED", "INCREASED", "DECREASED", "LIQUIDATED")


@dataclass
class PositionSnapshot:
    """
    Snapshot of all positions at a point in time
    
    Attributes:
        timestamp: When snapshot was taken
        address: Wallet address
        positions: Dictionary of coin -> LivePosition
        total_unrealized_pnl: Sum of all unrealized PnL
        total_margin_used: Sum of all margin used
        account_value: Total account value
        free_margin: Available margin
    """
    timestamp: datetime
    address: str
    positions: Dict[str, LivePosition] = field(default_factory=dict)
    total_unrealized_pnl: float = 0.0
    total_margin_used: float = 0.0
    account_value: float = 0.0
    free_margin: float = 0.0
    
    @property
    def open_positions_count(self) -> int:
        """Number of open positions"""
        return sum(1 for p in self.positions.values() if p.is_open())
    
    @property
    def total_notional(self) -> float:
        """Total notional value of all positions"""
        return sum(p.notional_value for p in self.positions.values())
    
    @property
    def margin_utilization(self) -> float:
        """Percentage of margin being used"""
        if self.account_value == 0:
            return 0.0
        return (self.total_margin_used / self.account_value) * 100
    
    def get_risky_positions(self, threshold: float = 10.0) -> List[LivePosition]:
        """
        Get positions that are at liquidation risk
        
        Args:
            threshold: Percentage threshold for risk
            
        Returns:
            List of at-risk positions
        """
        return [p for p in self.positions.values() if p.is_at_risk(threshold)]
    
    def get_largest_position(self) -> Optional[LivePosition]:
        """Get the position with largest notional value"""
        if not self.positions:
            return None
        return max(self.positions.values(), key=lambda p: p.notional_value)
    
    def get_most_profitable(self) -> Optional[LivePosition]:
        """Get position with highest unrealized PnL"""
        open_positions = [p for p in self.positions.values() if p.is_open()]
        if not open_positions:
            return None
        return max(open_positions, key=lambda p: p.unrealized_pnl)
    
    def get_most_losing(self) -> Optional[LivePosition]:
        """Get position with lowest unrealized PnL"""
        open_positions = [p for p in self.positions.values() if p.is_open()]
        if not open_positions:
            return None
        return min(open_positions, key=lambda p: p.unrealized_pnl)


@dataclass
class AccountState:
    """
    Overall account state including margin and equity
    
    Attributes:
        address: Wallet address
        account_value: Total account value (equity)
        total_margin_used: Total margin in use
        free_margin: Available margin
        withdrawable: Amount that can be withdrawn
        cross_margin_summary: Cross margin account info
        timestamp: When this state was fetched
    """
    address: str
    account_value: float = 0.0
    total_margin_used: float = 0.0
    free_margin: float = 0.0
    withdrawable: float = 0.0
    cross_margin_summary: Optional[Dict] = None
    timestamp: Optional[datetime] = None
    
    @property
    def margin_ratio(self) -> float:
        """Overall margin utilization percentage"""
        if self.account_value == 0:
            return 0.0
        return (self.total_margin_used / self.account_value) * 100
    
    @property
    def is_healthy(self) -> bool:
        """Check if account is in healthy state (margin ratio < 80%)"""
        return self.margin_ratio < 80.0
