"""Rule-based strategy signal generators for neural network training.

Generates entry signals based on documented profitable strategies for MOEX.
The NN learns to predict when these setups occur, combining rule-based
reliability with neural network pattern recognition.

Current strategies:
1. MACD + RSI Confluence — most documented (WR=78-86% in academic studies)
   LONG:  RSI(14) rising 3+ bars AND MACD histogram crosses 0 from below
   SHORT: RSI(14) falling 3+ bars AND MACD histogram crosses 0 from above
   Exit:  MACD_Diff reversal or forced close after max_hold_bars

SL/TP placement:
  - Fixed mode: uses tp_atr_mult/sl_atr_mult (legacy, default)
  - Dynamic mode: uses volatility-adaptive barriers (recommended)
    See ai/barrier_methods.py for details
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict, Optional

import numpy as np
import pandas as pd
from loguru import logger

from ai.features import calculate_rsi, calculate_macd
from ai.barrier_methods import suggest_dynamic_barriers


@dataclass
class StrategyConfig:
    """Configuration for strategy signal generation."""
    # MACD params
    macd_fast: int = 12
    macd_slow: int = 26
    macd_signal: int = 9
    # RSI params
    rsi_period: int = 14
    rsi_consecutive_bars: int = 3  # How many bars RSI must rise/fall
    # Entry filters
    min_atr_pct: float = 0.3  # Skip low-volatility bars
    max_spread_pct: float = 0.5  # Skip wide-spread bars
    # Exit params
    max_holding_bars: int = 6  # Force close after N bars
    # TP/SL
    tp_atr_mult: float = 2.0  # Default TP multiplier (fallback)
    sl_atr_mult: float = 1.0
    # Dynamic barriers (see ai/barrier_methods.py) — overfit, disabled by default
    enable_dynamic_barriers: bool = False  # Fixed barriers generalize better
    # Per-ticker TP multipliers (overrides tp_atr_mult for specific tickers)
    # Optimal values based on OOS 2024-2025 analysis:
    #   SBER=3.0, NVTK=3.0, PHOR=2.0, ROSN=3.0, VTBR=2.0, LKOH=2.5, ASTR=2.0
    tp_atr_mult_per_ticker: Dict[str, float] = field(default_factory=lambda: {
        "SBER": 3.0,
        "NVTK": 3.0,
        "PHOR": 2.0,
        "ROSN": 3.0,
        "VTBR": 2.0,
        "LKOH": 2.5,
        "ASTR": 2.0,
    })

    def get_tp_atr_mult(self, ticker: str = "") -> float:
        """Resolve TP multiplier for a ticker (per-ticker override or fallback to tp_atr_mult)."""
        return self.tp_atr_mult_per_ticker.get(ticker.upper(), self.tp_atr_mult)


def get_strategy_signals(
    df: pd.DataFrame,
    config: Optional[StrategyConfig] = None,
) -> pd.DataFrame:
    """Generate MACD+RSI confluence signals for all bars.

    Args:
        df: OHLCV DataFrame with columns [timestamp, Open, High, Low, Close, Volume]
        config: Strategy parameters

    Returns:
        DataFrame with columns:
        - timestamp: bar timestamp
        - strategy_signal: 1 if MACD+RSI setup detected, 0 otherwise
        - side: "LONG" or "SHORT" or None
        - rsi_trend: direction of RSI trend (-1=falling, 0=flat, 1=rising)
        - macd_cross: direction of MACD cross (-1=below zero, 1=above zero, 0=no cross)
        - signal_strength: combined confidence of the signal (0.0-1.0)
    """
    if config is None:
        config = StrategyConfig()

    if df.empty or len(df) < 50:
        return pd.DataFrame()

    df = df.sort_values("timestamp").reset_index(drop=True)

    # --- Calculate indicators ---
    # RSI(14)
    rsi = calculate_rsi(df["Close"], config.rsi_period)
    
    # MACD(12, 26, 9)
    macd_line, signal_line = calculate_macd(
        df["Close"],
        fast=config.macd_fast,
        slow=config.macd_slow,
        signal=config.macd_signal,
    )
    macd_hist = macd_line - signal_line  # MACD histogram
    macd_diff = macd_line  # MACD line value (for zero-cross)

    # ATR for filtering
    from ai.features import calculate_atr
    atr = calculate_atr(df, 14)
    atr_pct = atr / df["Close"] * 100

    # --- Compute strategy signals ---
    n = len(df)
    signals = np.zeros(n, dtype=float)
    sides = np.full(n, "", dtype=object)
    rsi_trends = np.zeros(n, dtype=int)
    macd_crosses = np.zeros(n, dtype=int)
    strengths = np.zeros(n, dtype=float)

    for i in range(config.rsi_consecutive_bars + 5, n):
        # --- Filter: skip low vol / wide spread ---
        if atr_pct.iloc[i] < config.min_atr_pct:
            continue

        # --- RSI trend: is it rising or falling for N bars? ---
        rsi_vals = rsi.iloc[i - config.rsi_consecutive_bars : i + 1].values
        rsi_rising = all(rsi_vals[j] > rsi_vals[j - 1] for j in range(1, len(rsi_vals)))
        rsi_falling = all(rsi_vals[j] < rsi_vals[j - 1] for j in range(1, len(rsi_vals)))

        rsi_trend = 0
        if rsi_rising:
            rsi_trend = 1
        elif rsi_falling:
            rsi_trend = -1
        rsi_trends[i] = rsi_trend

        # --- MACD histogram zero-cross ---
        macd_hist_prev = macd_hist.iloc[i - 1]
        macd_hist_curr = macd_hist.iloc[i]

        macd_cross = 0
        if macd_hist_prev < 0 and macd_hist_curr > 0:
            macd_cross = 1  # Crossed above zero → bullish
        elif macd_hist_prev > 0 and macd_hist_curr < 0:
            macd_cross = -1  # Crossed below zero → bearish
        macd_crosses[i] = macd_cross

        # --- LONG signal: RSI rising AND MACD hist crosses above zero ---
        if rsi_trend == 1 and macd_cross == 1:
            signals[i] = 1.0
            sides[i] = "LONG"
            # Signal strength = RSI slope + MACD hist magnitude
            rsi_slope = (rsi_vals[-1] - rsi_vals[0]) / max(config.rsi_consecutive_bars, 1)
            macd_strength = min(abs(macd_hist_curr) / (atr.iloc[i] + 1e-10), 1.0)
            strengths[i] = min((rsi_slope / 10 + macd_strength) / 2, 1.0)

        # --- SHORT signal: RSI falling AND MACD hist crosses below zero ---
        elif rsi_trend == -1 and macd_cross == -1:
            signals[i] = 1.0
            sides[i] = "SHORT"
            rsi_slope = abs(rsi_vals[-1] - rsi_vals[0]) / max(config.rsi_consecutive_bars, 1)
            macd_strength = min(abs(macd_hist_curr) / (atr.iloc[i] + 1e-10), 1.0)
            strengths[i] = min((rsi_slope / 10 + macd_strength) / 2, 1.0)

    result = pd.DataFrame({
        "timestamp": df["timestamp"],
        "strategy_signal": signals,
        "side": sides,
        "rsi_trend": rsi_trends,
        "macd_cross": macd_crosses,
        "signal_strength": strengths,
    })

    n_signals = int(signals.sum())
    if n_signals > 0:
        long_count = int((np.array(sides) == "LONG").sum())
        short_count = int((np.array(sides) == "SHORT").sum())
        logger.debug(
            f"MACD+RSI signals: {n_signals} всего "
            f"({long_count} LONG, {short_count} SHORT) "
            f"из {n} баров ({n_signals/n*100:.1f}%)"
        )

    return result


def backtest_strategy(
    df: pd.DataFrame,
    signals_df: pd.DataFrame,
    config: Optional[StrategyConfig] = None,
    capital: float = 1_000_000,
    risk_pct: float = 0.01,
    ticker: str = "",
) -> dict:
    """Simple backtest of MACD+RSI strategy.

    Args:
        df: OHLCV data
        signals_df: DataFrame from get_strategy_signals()
        config: Strategy parameters
        capital: Initial capital
        risk_pct: Risk per trade as fraction of capital
        ticker: Ticker name (for per-ticker TP resolution)

    Returns:
        Dict with backtest metrics
    """
    if config is None:
        config = StrategyConfig()

    if df.empty or signals_df.empty:
        return {"error": "No data"}

    df = df.sort_values("timestamp").reset_index(drop=True)
    signals_df = signals_df.sort_values("timestamp").reset_index(drop=True)

    # Merge signals into df
    df = df.copy()
    df["strategy_signal"] = signals_df["strategy_signal"].values
    df["strategy_side"] = signals_df["side"].values
    df["strategy_strength"] = signals_df["signal_strength"].values
    
    # ATR for TP/SL
    from ai.features import calculate_atr
    atr = calculate_atr(df, 14)
    df["ATR"] = atr.values

    trades = []
    equity = [capital]
    in_position = False
    entry_price = 0.0
    entry_bar = 0
    side = ""
    sl_price = 0.0
    tp_price = 0.0
    bars_in_trade = 0

    for i in range(len(df)):
        if in_position:
            bars_in_trade += 1
            high = df.iloc[i]["High"]
            low = df.iloc[i]["Low"]
            close = df.iloc[i]["Close"]

            # Check TP/SL for LONG
            if side == "LONG":
                if high >= tp_price:
                    # TP hit
                    pnl_pct = (tp_price - entry_price) / entry_price
                    trades.append(pnl_pct)
                    equity.append(equity[-1] * (1 + pnl_pct))
                    in_position = False
                elif low <= sl_price:
                    # SL hit
                    pnl_pct = (sl_price - entry_price) / entry_price
                    trades.append(pnl_pct)
                    equity.append(equity[-1] * (1 + pnl_pct))
                    in_position = False
            # Check TP/SL for SHORT
            elif side == "SHORT":
                if low <= tp_price:
                    pnl_pct = (entry_price - tp_price) / entry_price
                    trades.append(pnl_pct)
                    equity.append(equity[-1] * (1 + pnl_pct))
                    in_position = False
                elif high >= sl_price:
                    pnl_pct = (entry_price - sl_price) / entry_price
                    trades.append(pnl_pct)
                    equity.append(equity[-1] * (1 + pnl_pct))
                    in_position = False

            # Force close after max_holding_bars
            if in_position and bars_in_trade >= config.max_holding_bars:
                close_pnl = (close - entry_price) / entry_price if side == "LONG" else (entry_price - close) / entry_price
                trades.append(close_pnl)
                equity.append(equity[-1] * (1 + close_pnl))
                in_position = False

        # Check for new signal (only enter if not in position)
        if not in_position and df.iloc[i]["strategy_signal"] == 1:
            entry_price = df.iloc[i]["Close"]
            side = df.iloc[i]["strategy_side"]
            entry_bar = i
            bars_in_trade = 0
            atr_val = df.iloc[i]["ATR"]
            
            if config.enable_dynamic_barriers and "ATR" in df.columns:
                # Use volatility-adaptive SL/TP barriers
                # atr is a Series, df is a DataFrame — pass appropriate objects
                barrier_result = suggest_dynamic_barriers(
                    entry_price=entry_price,
                    side=side,
                    atr_val=atr_val,
                    atr_series=atr,
                    df=df,
                    idx=i,
                    use_structure=True,
                )
                sl_price = barrier_result.sl_price
                tp_price = barrier_result.tp_price
                
                logger.debug(
                    f"Dynamic barriers [{side}@{entry_price:.2f}]: "
                    f"SL={sl_price:.4f} TP={tp_price:.4f} "
                    f"({barrier_result.volatility_regime.value} vol, "
                    f"mult={barrier_result.sl_multiplier:.2f}/{barrier_result.tp_multiplier:.2f})"
                )
            else:
                # Fixed ATR multipliers with per-ticker TP support
                tp_mult = config.get_tp_atr_mult(ticker)
                if side == "LONG":
                    sl_price = entry_price - atr_val * config.sl_atr_mult
                    tp_price = entry_price + atr_val * tp_mult
                else:
                    sl_price = entry_price + atr_val * config.sl_atr_mult
                    tp_price = entry_price - atr_val * tp_mult
            
            in_position = True

    # Close any open position at end
    if in_position:
        close = df.iloc[-1]["Close"]
        close_pnl = (close - entry_price) / entry_price if side == "LONG" else (entry_price - close) / entry_price
        trades.append(close_pnl)
        equity.append(equity[-1] * (1 + close_pnl))

    if not trades:
        return {
            "total_trades": 0,
            "error": "No trades executed",
        }

    trades_arr = np.array(trades)
    wins = trades_arr > 0
    losses = trades_arr <= 0
    win_rate = wins.mean() if len(trades) > 0 else 0
    total_pnl = (equity[-1] / capital - 1) * capital if equity else 0
    avg_win = trades_arr[wins].mean() if wins.any() else 0
    avg_loss = abs(trades_arr[losses].mean()) if losses.any() else 0
    profit_factor = (trades_arr[wins].sum() / abs(trades_arr[losses].sum())) if losses.any() and trades_arr[losses].sum() != 0 else 0
    sharpe = (trades_arr.mean() / trades_arr.std() * np.sqrt(252)) if trades_arr.std() > 0 else 0

    return {
        "strategy": "MACD+RSI",
        "total_trades": len(trades),
        "win_rate": round(win_rate * 100, 1),
        "profit_factor": round(profit_factor, 2),
        "total_pnl": round(total_pnl, 0),
        "avg_win_pct": round(avg_win * 100, 2),
        "avg_loss_pct": round(avg_loss * 100, 2),
        "sharpe": round(sharpe, 2),
        "capital_end": round(equity[-1], 2) if equity else capital,
    }



