"""Performance Metrics and Quantitative Analytics."""
from __future__ import annotations

import math
from dataclasses import dataclass
from typing import Sequence


@dataclass
class BacktestReport:
    total_trades: int
    winning_trades: int
    losing_trades: int
    win_rate_pct: float
    net_pnl: float
    total_return_pct: float
    profit_factor: float
    average_win: float
    average_loss: float
    expectancy: float
    max_drawdown_pct: float
    sharpe_ratio: float
    sortino_ratio: float
    total_fees: float
    benchmark_buy_and_hold_return_pct: float
    benchmark_cash_return_pct: float = 0.0


def calculate_performance_metrics(
    initial_equity: float,
    equity_curve: Sequence[float],
    trade_pnls: Sequence[float],
    trade_fees: Sequence[float],
    benchmark_buy_and_hold_return: float = 0.0,
) -> BacktestReport:
    """Calculate institutional-grade quantitative metrics."""
    total_trades = len(trade_pnls)
    if total_trades == 0 or len(equity_curve) < 2:
        return BacktestReport(
            total_trades=0,
            winning_trades=0,
            losing_trades=0,
            win_rate_pct=0.0,
            net_pnl=0.0,
            total_return_pct=0.0,
            profit_factor=0.0,
            average_win=0.0,
            average_loss=0.0,
            expectancy=0.0,
            max_drawdown_pct=0.0,
            sharpe_ratio=0.0,
            sortino_ratio=0.0,
            total_fees=sum(trade_fees),
            benchmark_buy_and_hold_return_pct=benchmark_buy_and_hold_return,
        )

    wins = [p for p in trade_pnls if p > 0]
    losses = [p for p in trade_pnls if p < 0]

    winning_trades = len(wins)
    losing_trades = len(losses)
    win_rate = (winning_trades / total_trades) * 100.0 if total_trades > 0 else 0.0

    gross_profit = sum(wins)
    gross_loss = abs(sum(losses))
    profit_factor = (gross_profit / gross_loss) if gross_loss > 0 else (99.0 if gross_profit > 0 else 0.0)

    avg_win = (sum(wins) / len(wins)) if wins else 0.0
    avg_loss = (abs(sum(losses)) / len(losses)) if losses else 0.0

    win_prob = winning_trades / total_trades
    loss_prob = losing_trades / total_trades
    expectancy = (win_prob * avg_win) - (loss_prob * avg_loss)

    final_equity = equity_curve[-1]
    net_pnl = final_equity - initial_equity
    total_return_pct = (net_pnl / initial_equity) * 100.0

    # Max Drawdown
    peak = initial_equity
    max_dd = 0.0
    for eq in equity_curve:
        if eq > peak:
            peak = eq
        dd = (peak - eq) / peak if peak > 0 else 0.0
        if dd > max_dd:
            max_dd = dd

    # Returns series for Sharpe and Sortino
    returns = []
    for i in range(1, len(equity_curve)):
        prev = equity_curve[i - 1]
        r = (equity_curve[i] - prev) / prev if prev > 0 else 0.0
        returns.append(r)

    # Annualized Sharpe (assuming 15m intervals ~ 35,040 bars/yr)
    mean_ret = sum(returns) / len(returns) if returns else 0.0
    var = sum((r - mean_ret) ** 2 for r in returns) / len(returns) if len(returns) > 1 else 0.0
    std_ret = math.sqrt(var) if var > 0 else 0.0

    annual_factor = math.sqrt(35040)
    sharpe = (mean_ret / std_ret * annual_factor) if std_ret > 0 else 0.0

    # Downside deviation for Sortino
    downside_returns = [r for r in returns if r < 0]
    downside_var = sum(r**2 for r in downside_returns) / len(returns) if downside_returns else 0.0
    downside_std = math.sqrt(downside_var) if downside_var > 0 else 0.0
    sortino = (mean_ret / downside_std * annual_factor) if downside_std > 0 else 0.0

    return BacktestReport(
        total_trades=total_trades,
        winning_trades=winning_trades,
        losing_trades=losing_trades,
        win_rate_pct=round(win_rate, 2),
        net_pnl=round(net_pnl, 2),
        total_return_pct=round(total_return_pct, 2),
        profit_factor=round(profit_factor, 2),
        average_win=round(avg_win, 2),
        average_loss=round(avg_loss, 2),
        expectancy=round(expectancy, 2),
        max_drawdown_pct=round(max_dd * 100.0, 2),
        sharpe_ratio=round(sharpe, 2),
        sortino_ratio=round(sortino, 2),
        total_fees=round(sum(trade_fees), 2),
        benchmark_buy_and_hold_return_pct=round(benchmark_buy_and_hold_return, 2),
    )
