# src/backtest/trade.py
"""
Clases para representar trades y posiciones
"""
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional

class TradeType(Enum):
    """Tipo de trade"""
    LONG = "LONG"
    SHORT = "SHORT"

class TradeStatus(Enum):
    """Estado del trade"""
    OPEN = "OPEN"
    CLOSED = "CLOSED"

@dataclass
class Trade:
    """
    Representa un trade individual
    """
    symbol: str
    trade_type: TradeType
    entry_price: float
    entry_time: datetime
    size: float
    exit_price: Optional[float] = None
    exit_time: Optional[datetime] = None
    status: TradeStatus = field(default=TradeStatus.OPEN)
    stop_price_at_entry: Optional[float] = None
    capital_at_entry: Optional[float] = None
    
    # Costes
    entry_commission: float = 0.0
    exit_commission: float = 0.0

    #Razones
    entry_reason: str = ""
    exit_reason: str = ""
    
    @property
    def pnl(self) -> float:
        """
        Calcula profit and loss del trade
        """
        if self.status == TradeStatus.OPEN or self.exit_price is None:
            return 0.0
        
        if self.trade_type == TradeType.LONG:
            raw_pnl = (self.exit_price - self.entry_price) * self.size
        else:
            raw_pnl = (self.entry_price - self.exit_price) * self.size
        
        total_pnl = raw_pnl - self.entry_commission - self.exit_commission
        return total_pnl
    
    @property
    def pnl_percentage(self) -> float:
        """
        Retorno porcentual del trade
        """
        if self.status == TradeStatus.OPEN or self.exit_price is None:
            return 0.0
        
        investment = self.entry_price * self.size
        return (self.pnl / investment) * 100
    
    @property
    def duration(self) -> Optional[float]:
        """
        Duración del trade en horas
        """
        if self.exit_time is None:
            return None
        
        delta = self.exit_time - self.entry_time
        return delta.total_seconds() / 3600
    
    def close(self, exit_price: float, exit_time: datetime, reason: str = ""):
        """
        Cierra el trade
        """
        self.exit_price = exit_price
        self.exit_time = exit_time
        self.status = TradeStatus.CLOSED
        self.exit_reason = reason
    
    def __repr__(self):
        status_str = "OPEN" if self.status == TradeStatus.OPEN else f"CLOSED (PnL: {self.pnl:.2f})"
        return (f"Trade({self.trade_type.value} {self.symbol} @ {self.entry_price:.2f}, "
                f"Size: {self.size:.4f}, {status_str})")


@dataclass
class Position:
    """
    Representa una posición activa (puede contener múltiples trades)
    """
    symbol: str
    trade_type: TradeType
    average_price: float
    total_size: float
    trades: list = field(default_factory=list)
    stop_price: Optional[float] = None
    initial_stop_price: Optional[float] = None
    
    def add_trade(self, trade: Trade):
        """
        Añade un trade a la posición y actualiza el precio promedio
        """
        total_cost = self.average_price * self.total_size
        new_cost = trade.entry_price * trade.size
        
        self.total_size += trade.size
        self.average_price = (total_cost + new_cost) / self.total_size
        self.trades.append(trade)
    
    def close_all(self, exit_price: float, exit_time: datetime, reason: str = ""):
        """
        Cierra todos los trades de la posición
        """
        for trade in self.trades:
            if trade.status == TradeStatus.OPEN:
                trade.close(exit_price, exit_time, reason)
    
    def unrealized_pnl(self, current_price: float) -> float:
        """
        PnL no realizado basado en el precio actual
        """
        if self.trade_type == TradeType.LONG:
            return (current_price - self.average_price) * self.total_size
        else:
            return (self.average_price - current_price) * self.total_size
        
    def set_stop(self, price: float):
        """
        Establece el stop de la posición.
        Guarda el stop inicial si aún no existe.
        """
        if price is None or price <= 0:
            raise ValueError("stop_price inválido")
        
        if self.initial_stop_price is None:
            self.initial_stop_price = price

        self.stop_price = price

    def is_stop_hit(self, current_price: float):
        """
        Devuelve True si el stop ha sido alcanzado.
        """
        if self.stop_price is None:
            return False
        
        if self.trade_type == TradeType.LONG:
            return current_price <= self.stop_price
        
        elif self.trade_type == TradeType.SHORT:
            return current_price >= self.stop_price
        
        return False

    def move_stop(self, new_price: float):
        """
        Mueve el stop solo si reduce el riesgo.
        """
        if new_price is None or new_price <= 0:
            return
        
        if self.stop_price is None:
            self.set_stop(new_price)
            return
        
        # LONG → solo subir stop
        if self.trade_type == TradeType.LONG and new_price > self.stop_price:
            self.stop_price = new_price

        # Short → solo bajar stop
        elif self.trade_type == TradeType.SHORT and new_price < self.stop_price:
            self.stop_price = new_price