"""
Trade data structures for backtesting engine.

This module provides data classes and enums for representing trades,
their directions, and status throughout the backtesting process.
"""

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional, Dict, Any


class TradeDirection(Enum):
    """Direction of a trade."""

    LONG = "long"
    SHORT = "short"

    def __str__(self) -> str:
        """Return string representation."""
        return self.value

    @classmethod
    def from_string(cls, value: str) -> "TradeDirection":
        """
        Create TradeDirection from string.

        Args:
            value: String value ('long' or 'short')

        Returns:
            TradeDirection enum value

        Raises:
            ValueError: If value is not valid
        """
        value_lower = value.lower()
        if value_lower == "long":
            return cls.LONG
        elif value_lower == "short":
            return cls.SHORT
        else:
            raise ValueError(f"Invalid trade direction: {value}")


class TradeStatus(Enum):
    """Status of a trade throughout its lifecycle."""

    OPEN = "open"
    CLOSED = "closed"
    STOPPED_OUT = "stopped_out"
    TAKE_PROFIT = "take_profit"
    TIMEOUT = "timeout"
    CANCELLED = "cancelled"

    def __str__(self) -> str:
        """Return string representation."""
        return self.value

    @property
    def is_closed(self) -> bool:
        """Check if trade is in a closed state."""
        return self in (
            TradeStatus.CLOSED,
            TradeStatus.STOPPED_OUT,
            TradeStatus.TAKE_PROFIT,
            TradeStatus.TIMEOUT,
            TradeStatus.CANCELLED
        )

    @property
    def is_profitable_exit(self) -> bool:
        """Check if exit status typically indicates profit."""
        return self == TradeStatus.TAKE_PROFIT


@dataclass
class Trade:
    """
    Represents a single trade in the backtesting system.

    This class captures all information about a trade from entry to exit,
    including pricing, timing, sizing, and profit/loss calculations.

    Attributes:
        trade_id: Unique identifier for the trade
        symbol: Trading instrument symbol (e.g., 'EURUSD', 'BTCUSD')
        direction: Trade direction (LONG or SHORT)
        entry_price: Price at which the trade was entered
        exit_price: Price at which the trade was closed (None if still open)
        entry_time: Timestamp when trade was opened
        exit_time: Timestamp when trade was closed (None if still open)
        size: Position size in units/lots
        stop_loss: Stop loss price level
        take_profit: Take profit price level
        status: Current status of the trade
        pnl: Realized profit/loss in account currency
        pnl_pips: Profit/loss in pips (for forex)
        pnl_percent: Profit/loss as percentage of entry value
        commission: Commission paid for the trade
        slippage: Slippage incurred on the trade
        strategy_name: Name of the strategy that generated this trade
        timeframe: Timeframe on which the trade was generated
        signal_confidence: Confidence score of the entry signal
        metadata: Additional trade metadata
    """

    trade_id: int
    symbol: str
    direction: TradeDirection
    entry_price: float
    entry_time: datetime
    size: float

    exit_price: Optional[float] = None
    exit_time: Optional[datetime] = None
    stop_loss: Optional[float] = None
    take_profit: Optional[float] = None
    status: TradeStatus = TradeStatus.OPEN

    pnl: float = 0.0
    pnl_pips: float = 0.0
    pnl_percent: float = 0.0
    commission: float = 0.0
    slippage: float = 0.0

    strategy_name: str = "default"
    timeframe: str = "1H"
    signal_confidence: float = 0.0

    metadata: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        """Validate trade data after initialization."""
        if self.entry_price <= 0:
            raise ValueError("Entry price must be positive")
        if self.size <= 0:
            raise ValueError("Position size must be positive")
        if isinstance(self.direction, str):
            self.direction = TradeDirection.from_string(self.direction)
        if isinstance(self.status, str):
            self.status = TradeStatus(self.status)

    @property
    def is_open(self) -> bool:
        """Check if trade is still open."""
        return self.status == TradeStatus.OPEN

    @property
    def is_closed(self) -> bool:
        """Check if trade is closed."""
        return self.status.is_closed

    @property
    def is_long(self) -> bool:
        """Check if this is a long trade."""
        return self.direction == TradeDirection.LONG

    @property
    def is_short(self) -> bool:
        """Check if this is a short trade."""
        return self.direction == TradeDirection.SHORT

    @property
    def is_profitable(self) -> bool:
        """Check if trade is profitable."""
        return self.pnl > 0

    @property
    def duration_seconds(self) -> Optional[float]:
        """Get trade duration in seconds."""
        if self.exit_time is None:
            return None
        return (self.exit_time - self.entry_time).total_seconds()

    @property
    def duration_minutes(self) -> Optional[float]:
        """Get trade duration in minutes."""
        seconds = self.duration_seconds
        if seconds is None:
            return None
        return seconds / 60

    @property
    def duration_hours(self) -> Optional[float]:
        """Get trade duration in hours."""
        seconds = self.duration_seconds
        if seconds is None:
            return None
        return seconds / 3600

    @property
    def risk_reward_achieved(self) -> Optional[float]:
        """Calculate achieved risk/reward ratio."""
        if self.stop_loss is None or self.exit_price is None:
            return None

        if self.is_long:
            risk = self.entry_price - self.stop_loss
            reward = self.exit_price - self.entry_price
        else:
            risk = self.stop_loss - self.entry_price
            reward = self.entry_price - self.exit_price

        if risk <= 0:
            return None

        return reward / risk

    def close(
        self,
        exit_price: float,
        exit_time: datetime,
        status: TradeStatus = TradeStatus.CLOSED,
        commission: float = 0.0,
        slippage: float = 0.0
    ) -> float:
        """
        Close the trade and calculate P&L.

        Args:
            exit_price: Price at which to close the trade
            exit_time: Time of trade closure
            status: Exit status (CLOSED, STOPPED_OUT, TAKE_PROFIT, TIMEOUT)
            commission: Commission for closing the trade
            slippage: Slippage incurred on exit

        Returns:
            Realized profit/loss

        Raises:
            ValueError: If trade is already closed
        """
        if self.is_closed:
            raise ValueError(f"Trade {self.trade_id} is already closed")

        self.exit_price = exit_price
        self.exit_time = exit_time
        self.status = status
        self.commission += commission
        self.slippage += slippage

        self._calculate_pnl()

        return self.pnl

    def _calculate_pnl(self) -> None:
        """Calculate profit/loss based on entry and exit prices."""
        if self.exit_price is None:
            return

        price_diff = self.exit_price - self.entry_price

        if self.is_short:
            price_diff = -price_diff

        gross_pnl = price_diff * self.size
        self.pnl = gross_pnl - self.commission - self.slippage

        entry_value = self.entry_price * self.size
        if entry_value > 0:
            self.pnl_percent = (self.pnl / entry_value) * 100

        pip_value = self._get_pip_value()
        if pip_value > 0:
            self.pnl_pips = price_diff / pip_value

    def _get_pip_value(self) -> float:
        """
        Get pip value for the symbol.

        Returns:
            Pip value (0.0001 for most forex, 0.01 for JPY pairs, 1.0 for crypto)
        """
        symbol_upper = self.symbol.upper()

        if "JPY" in symbol_upper:
            return 0.01
        elif any(crypto in symbol_upper for crypto in ["BTC", "ETH", "XRP", "LTC"]):
            return 1.0
        else:
            return 0.0001

    def calculate_unrealized_pnl(self, current_price: float) -> float:
        """
        Calculate unrealized P&L at current price.

        Args:
            current_price: Current market price

        Returns:
            Unrealized profit/loss
        """
        if self.is_closed:
            return self.pnl

        price_diff = current_price - self.entry_price

        if self.is_short:
            price_diff = -price_diff

        return price_diff * self.size

    def should_stop_out(self, current_price: float) -> bool:
        """
        Check if current price triggers stop loss.

        Args:
            current_price: Current market price

        Returns:
            True if stop loss should be triggered
        """
        if self.stop_loss is None:
            return False

        if self.is_long:
            return current_price <= self.stop_loss
        else:
            return current_price >= self.stop_loss

    def should_take_profit(self, current_price: float) -> bool:
        """
        Check if current price triggers take profit.

        Args:
            current_price: Current market price

        Returns:
            True if take profit should be triggered
        """
        if self.take_profit is None:
            return False

        if self.is_long:
            return current_price >= self.take_profit
        else:
            return current_price <= self.take_profit

    def to_dict(self) -> Dict[str, Any]:
        """
        Convert trade to dictionary representation.

        Returns:
            Dictionary with all trade attributes
        """
        return {
            "trade_id": self.trade_id,
            "symbol": self.symbol,
            "direction": str(self.direction),
            "entry_price": self.entry_price,
            "exit_price": self.exit_price,
            "entry_time": self.entry_time.isoformat() if self.entry_time else None,
            "exit_time": self.exit_time.isoformat() if self.exit_time else None,
            "size": self.size,
            "stop_loss": self.stop_loss,
            "take_profit": self.take_profit,
            "status": str(self.status),
            "pnl": self.pnl,
            "pnl_pips": self.pnl_pips,
            "pnl_percent": self.pnl_percent,
            "commission": self.commission,
            "slippage": self.slippage,
            "strategy_name": self.strategy_name,
            "timeframe": self.timeframe,
            "signal_confidence": self.signal_confidence,
            "duration_minutes": self.duration_minutes,
            "risk_reward_achieved": self.risk_reward_achieved,
            "metadata": self.metadata
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Trade":
        """
        Create Trade instance from dictionary.

        Args:
            data: Dictionary with trade data

        Returns:
            Trade instance
        """
        entry_time = data.get("entry_time")
        if isinstance(entry_time, str):
            entry_time = datetime.fromisoformat(entry_time)

        exit_time = data.get("exit_time")
        if isinstance(exit_time, str):
            exit_time = datetime.fromisoformat(exit_time)

        return cls(
            trade_id=data["trade_id"],
            symbol=data["symbol"],
            direction=TradeDirection.from_string(data["direction"]),
            entry_price=data["entry_price"],
            entry_time=entry_time,
            size=data["size"],
            exit_price=data.get("exit_price"),
            exit_time=exit_time,
            stop_loss=data.get("stop_loss"),
            take_profit=data.get("take_profit"),
            status=TradeStatus(data.get("status", "open")),
            pnl=data.get("pnl", 0.0),
            pnl_pips=data.get("pnl_pips", 0.0),
            pnl_percent=data.get("pnl_percent", 0.0),
            commission=data.get("commission", 0.0),
            slippage=data.get("slippage", 0.0),
            strategy_name=data.get("strategy_name", "default"),
            timeframe=data.get("timeframe", "1H"),
            signal_confidence=data.get("signal_confidence", 0.0),
            metadata=data.get("metadata", {})
        )

    def __repr__(self) -> str:
        """Return string representation of trade."""
        status_str = "OPEN" if self.is_open else f"CLOSED ({self.pnl:+.2f})"
        return (
            f"Trade(id={self.trade_id}, {self.symbol}, {self.direction.value.upper()}, "
            f"entry={self.entry_price:.5f}, size={self.size:.4f}, {status_str})"
        )
