"""Classe base astratta per tutte le strategie di trading."""
from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field

import numpy as np
import pandas as pd

from src.data.downloader import load_data


@dataclass
class Signal:
    """Segnale di trading generato da una strategia."""
    idx: int
    direction: int          # +1 long, -1 short
    entry_price: float
    metadata: dict = field(default_factory=dict)


@dataclass
class YearlyStats:
    year: int
    trades: int
    wins: int
    pnl: float

    @property
    def accuracy(self) -> float:
        return self.wins / self.trades * 100 if self.trades > 0 else 0.0


@dataclass
class BacktestResult:
    """Risultato completo di un backtest."""
    strategy_name: str
    asset: str
    timeframe: str
    params: dict

    trades: int
    wins: int
    pnl: float
    capital: float
    initial_capital: float
    max_dd: float
    time_in_market_pct: float
    avg_trade_duration_h: float
    years_active: int
    yearly: list[YearlyStats]

    @property
    def accuracy(self) -> float:
        return self.wins / self.trades * 100 if self.trades > 0 else 0.0

    @property
    def sharpe(self) -> float:
        pnls = []
        for ys in self.yearly:
            pnls.append(ys.pnl)
        if len(pnls) < 2 or np.std(pnls) == 0:
            return 0.0
        return float(np.mean(pnls) / np.std(pnls) * np.sqrt(len(pnls)))

    @property
    def daily_pnl(self) -> float:
        return self.pnl / (self.years_active * 365) if self.years_active > 0 else 0.0

    @property
    def worst_year(self) -> YearlyStats | None:
        valid = [y for y in self.yearly if y.trades >= 10]
        if not valid:
            valid = self.yearly
        return min(valid, key=lambda y: y.accuracy) if valid else None

    def print_summary(self):
        worst = self.worst_year
        worst_str = f"{worst.year}({worst.accuracy:.0f}%)" if worst else "N/A"
        dur = f"{self.avg_trade_duration_h:.0f}h" if self.avg_trade_duration_h >= 1 else f"{self.avg_trade_duration_h * 60:.0f}m"
        print(f"  {self.strategy_name:<30s} {self.asset:>3s} {self.timeframe:>3s} "
              f"{self.trades:>5d}t {self.accuracy:>5.1f}% "
              f"€{self.pnl:>+9.0f} DD {self.max_dd:>4.1f}% "
              f"€/d {self.daily_pnl:>+6.2f} "
              f"Mkt {self.time_in_market_pct:>4.1f}% {dur:>5s} "
              f"worst={worst_str} {self.years_active}y")

    def print_yearly(self):
        print(f"\n  {self.strategy_name} [{self.asset} {self.timeframe}] — per anno:")
        print(f"  {'Anno':>6s} {'Trades':>7s} {'Acc':>6s} {'PnL€':>9s}")
        for ys in sorted(self.yearly, key=lambda y: y.year):
            print(f"  {ys.year:>6d} {ys.trades:>7d} {ys.accuracy:>5.1f}% €{ys.pnl:>+8.0f}")


TF_MINUTES = {"1m": 1, "5m": 5, "15m": 15, "1h": 60, "4h": 240, "1d": 1440}


class Strategy(ABC):
    """Classe base per tutte le strategie.

    Sottoclassi devono implementare:
      - name, description, default_assets, default_timeframes
      - generate_signals(df, timestamps, **params) -> list[Signal]
    """

    name: str = "unnamed"
    description: str = ""
    default_assets: list[str] = ["BTC", "ETH"]
    default_timeframes: list[str] = ["15m", "1h"]

    # Parametri di backtest
    fee_rt: float = 0.002
    leverage: float = 3.0
    position_size: float = 0.15
    initial_capital: float = 1000.0

    @abstractmethod
    def generate_signals(self, df: pd.DataFrame, ts: pd.DatetimeIndex,
                         **params) -> list[Signal]:
        """Genera segnali di trading dal dataframe OHLCV.

        Args:
            df: DataFrame con colonne open, high, low, close, volume, timestamp
            ts: DatetimeIndex UTC dei timestamp
            **params: parametri specifici della strategia

        Returns:
            Lista di Signal con idx, direction, entry_price
        """
        ...

    def backtest(self, asset: str, tf: str, hold: int = 3,
                 **params) -> BacktestResult | None:
        """Esegue backtest su un asset/timeframe."""
        df = load_data(asset, tf)
        c = df["close"].values
        n = len(c)
        ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)

        sig_params = {**params, "asset": asset, "tf": tf}
        signals = self.generate_signals(df, ts, **sig_params)
        if not signals:
            return None

        yearly: dict[int, dict] = {}
        capital = float(self.initial_capital)
        peak = capital
        max_dd = 0.0
        total_bars = 0

        for sig in signals:
            i = sig.idx
            if i + hold >= n or i < 1:
                continue

            entry = sig.entry_price
            exit_price = c[min(i + hold - 1, n - 1)]
            actual = (exit_price - entry) / entry * sig.direction
            net = actual * self.leverage - self.fee_rt * self.leverage

            capital += capital * self.position_size * net
            capital = max(capital, 10)
            if capital > peak:
                peak = capital
            dd = (peak - capital) / peak
            max_dd = max(max_dd, dd)
            total_bars += hold

            year = ts.iloc[i].year
            if year not in yearly:
                yearly[year] = {"w": 0, "t": 0, "pnl": 0.0}
            yearly[year]["t"] += 1
            if actual > 0:
                yearly[year]["w"] += 1
            yearly[year]["pnl"] += net * self.initial_capital

        all_t = sum(d["t"] for d in yearly.values())
        all_w = sum(d["w"] for d in yearly.values())
        if all_t == 0:
            return None

        yearly_stats = [
            YearlyStats(year=y, trades=d["t"], wins=d["w"], pnl=d["pnl"])
            for y, d in sorted(yearly.items())
        ]

        return BacktestResult(
            strategy_name=self.name,
            asset=asset,
            timeframe=tf,
            params=params,
            trades=all_t,
            wins=all_w,
            pnl=sum(d["pnl"] for d in yearly.values()),
            capital=capital,
            initial_capital=self.initial_capital,
            max_dd=max_dd * 100,
            time_in_market_pct=total_bars / n * 100,
            avg_trade_duration_h=hold * TF_MINUTES.get(tf, 60) / 60,
            years_active=len(yearly),
            yearly=yearly_stats,
        )

    def run_all(self, assets: list[str] | None = None,
                timeframes: list[str] | None = None,
                hold: int = 3, **params) -> list[BacktestResult]:
        """Esegue backtest su tutte le combinazioni asset/timeframe."""
        assets = assets or self.default_assets
        timeframes = timeframes or self.default_timeframes
        results = []
        for asset in assets:
            for tf in timeframes:
                r = self.backtest(asset, tf, hold=hold, **params)
                if r and r.trades >= 20:
                    results.append(r)
        results.sort(key=lambda r: r.accuracy, reverse=True)
        return results

    def report(self, results: list[BacktestResult] | None = None,
               assets: list[str] | None = None,
               timeframes: list[str] | None = None,
               hold: int = 3, **params):
        """Esegue e stampa report completo."""
        if results is None:
            results = self.run_all(assets, timeframes, hold, **params)

        print(f"\n{'=' * 120}")
        print(f"  {self.name} — {self.description}")
        print(f"  Fee: {self.fee_rt*100:.1f}% RT | Leva: {self.leverage:.0f}x | Pos: {self.position_size*100:.0f}%")
        print(f"{'=' * 120}")
        print(f"  {'Nome':<30s} {'A/T':>7s} {'Trades':>6s} {'Acc':>6s} "
              f"{'PnL€':>10s} {'DD%':>6s} {'€/day':>7s} "
              f"{'Mkt%':>5s} {'Dur':>5s} {'Worst':>12s} {'Anni':>4s}")
        print(f"  {'─' * 110}")

        for r in results:
            r.print_summary()

        if results:
            best = results[0]
            best.print_yearly()

        return results
