"""Backtesting engine with fee support and performance metrics."""
from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum

import numpy as np
import pandas as pd


class Side(Enum):
    LONG = 1
    SHORT = -1


@dataclass
class Trade:
    entry_idx: int
    exit_idx: int
    side: Side
    entry_price: float
    exit_price: float
    size: float
    fee_pct: float

    @property
    def gross_pnl(self) -> float:
        if self.side == Side.LONG:
            return (self.exit_price - self.entry_price) * self.size
        return (self.entry_price - self.exit_price) * self.size

    @property
    def fee(self) -> float:
        return self.fee_pct * (self.entry_price + self.exit_price) * self.size

    @property
    def net_pnl(self) -> float:
        return self.gross_pnl - self.fee

    @property
    def net_return(self) -> float:
        cost = self.entry_price * self.size + self.fee_pct * self.entry_price * self.size
        if cost == 0:
            return 0.0
        return self.net_pnl / cost


@dataclass
class BacktestResult:
    trades: list[Trade]
    initial_capital: float
    final_capital: float
    equity_curve: list[float]

    @property
    def total_trades(self) -> int:
        return len(self.trades)

    @property
    def win_rate(self) -> float:
        if not self.trades:
            return 0.0
        wins = sum(1 for t in self.trades if t.net_pnl > 0)
        return wins / len(self.trades)

    @property
    def total_return(self) -> float:
        if self.initial_capital == 0:
            return 0.0
        return (self.final_capital - self.initial_capital) / self.initial_capital

    @property
    def annualized_return(self) -> float:
        if not self.trades or self.total_return <= -1:
            return -1.0
        days = (self.trades[-1].exit_idx - self.trades[0].entry_idx) / 24
        if days <= 0:
            return 0.0
        years = days / 365.25
        if years == 0:
            return 0.0
        return (1 + self.total_return) ** (1 / years) - 1

    @property
    def max_drawdown(self) -> float:
        if not self.equity_curve:
            return 0.0
        peak = self.equity_curve[0]
        max_dd = 0.0
        for val in self.equity_curve:
            if val > peak:
                peak = val
            dd = (peak - val) / peak if peak > 0 else 0
            if dd > max_dd:
                max_dd = dd
        return max_dd

    @property
    def sharpe_ratio(self) -> float:
        if len(self.equity_curve) < 2:
            return 0.0
        eq = np.array(self.equity_curve)
        returns = np.diff(eq) / eq[:-1]
        returns = returns[np.isfinite(returns)]
        if len(returns) == 0 or np.std(returns) == 0:
            return 0.0
        return float(np.mean(returns) / np.std(returns) * np.sqrt(252 * 24))

    @property
    def profit_factor(self) -> float:
        gross_wins = sum(t.net_pnl for t in self.trades if t.net_pnl > 0)
        gross_losses = abs(sum(t.net_pnl for t in self.trades if t.net_pnl < 0))
        if gross_losses == 0:
            return float("inf") if gross_wins > 0 else 0.0
        return gross_wins / gross_losses

    def summary(self) -> dict:
        return {
            "total_trades": self.total_trades,
            "win_rate": round(self.win_rate * 100, 1),
            "total_return_pct": round(self.total_return * 100, 1),
            "annualized_return_pct": round(self.annualized_return * 100, 1),
            "max_drawdown_pct": round(self.max_drawdown * 100, 1),
            "sharpe_ratio": round(self.sharpe_ratio, 2),
            "profit_factor": round(self.profit_factor, 2),
            "initial_capital": self.initial_capital,
            "final_capital": round(self.final_capital, 2),
        }


def run_backtest(
    df: pd.DataFrame,
    signals: pd.Series,
    initial_capital: float = 1000.0,
    fee_pct: float = 0.001,
    position_size_pct: float = 1.0,
    max_hold_candles: int = 24,
) -> BacktestResult:
    """Run backtest on signals.

    signals: Series with same index as df.
        +1 = go long, -1 = go short, 0 = no signal
    """
    capital = initial_capital
    trades: list[Trade] = []
    equity_curve: list[float] = [capital]
    in_position = False
    entry_idx = 0
    entry_price = 0.0
    current_side = Side.LONG
    size = 0.0

    for i in range(len(df)):
        sig = signals.iloc[i] if i < len(signals) else 0

        if in_position:
            hold_time = i - entry_idx
            exit_price = df["close"].iloc[i]
            should_exit = (
                hold_time >= max_hold_candles
                or (current_side == Side.LONG and sig == -1)
                or (current_side == Side.SHORT and sig == 1)
            )

            if should_exit:
                trade = Trade(
                    entry_idx=entry_idx,
                    exit_idx=i,
                    side=current_side,
                    entry_price=entry_price,
                    exit_price=exit_price,
                    size=size,
                    fee_pct=fee_pct,
                )
                capital += trade.net_pnl
                trades.append(trade)
                in_position = False

        if not in_position and sig != 0 and capital > 0:
            entry_idx = i
            entry_price = df["close"].iloc[i]
            current_side = Side.LONG if sig > 0 else Side.SHORT
            alloc = capital * position_size_pct
            size = alloc / entry_price
            in_position = True

        equity_curve.append(capital)

    if in_position:
        exit_price = df["close"].iloc[-1]
        trade = Trade(
            entry_idx=entry_idx,
            exit_idx=len(df) - 1,
            side=current_side,
            entry_price=entry_price,
            exit_price=exit_price,
            size=size,
            fee_pct=fee_pct,
        )
        capital += trade.net_pnl
        trades.append(trade)
        equity_curve.append(capital)

    return BacktestResult(
        trades=trades,
        initial_capital=initial_capital,
        final_capital=capital,
        equity_curve=equity_curve,
    )
