"""
Signal generation engine.
Combines EMA crossover, ADX trend strength, RSI momentum, and volume
confirmation to produce a directional signal with confidence score.
"""
import logging
from typing import List, Optional

from tabdeal_bot.exchange.models import Candle, Direction, Signal
from tabdeal_bot.config.settings import StrategyConfig, TradingConfig
from tabdeal_bot.strategy.indicators import (
    ema, atr, rsi, adx, volume_ratio, find_swing_levels
)

logger = logging.getLogger(__name__)


class SignalEngine:
    def __init__(self, strategy_cfg: StrategyConfig, trading_cfg: TradingConfig):
        self.sc = strategy_cfg
        self.tc = trading_cfg

    def analyze(self, candles: List[Candle]) -> Optional[Signal]:
        """
        Analyze candles and return a Signal if conditions are met, else None.
        Requires at least (ema_slow + atr_period + 10) candles.
        """
        min_candles = self.sc.ema_slow + self.sc.atr_period + 5
        if len(candles) < min_candles:
            logger.warning("Not enough candles: %d < %d", len(candles), min_candles)
            return None

        closes = [c.close for c in candles]
        highs = [c.high for c in candles]
        lows = [c.low for c in candles]
        volumes = [c.volume for c in candles]

        # --- Indicators ---
        ema_fast_arr = ema(closes, self.sc.ema_fast)
        ema_slow_arr = ema(closes, self.sc.ema_slow)
        current_atr = atr(highs, lows, closes, self.sc.atr_period)
        current_rsi = rsi(closes, self.sc.rsi_period)
        current_adx = adx(highs, lows, closes, self.sc.atr_period)
        vol_ratio = volume_ratio(volumes, 20)
        swings = find_swing_levels(highs, lows, 20)

        ef_now = ema_fast_arr[-1]
        es_now = ema_slow_arr[-1]
        ef_prev = ema_fast_arr[-2]
        es_prev = ema_slow_arr[-2]
        price = closes[-1]

        # --- Trend direction via EMA ---
        ema_bullish = ef_now > es_now
        ema_bearish = ef_now < es_now

        # Fresh crossover bonus
        crossed_up = ef_prev <= es_prev and ef_now > es_now
        crossed_down = ef_prev >= es_prev and ef_now < es_now

        # --- ADX: reject if no trend ---
        if current_adx < self.sc.min_adx:
            logger.info("ADX %.1f below minimum %.1f — no signal", current_adx, self.sc.min_adx)
            return None

        # --- RSI filter: avoid extreme exhaustion on entry ---
        rsi_ok_long = current_rsi < self.sc.rsi_overbought
        rsi_ok_short = current_rsi > self.sc.rsi_oversold

        # --- Volume confirmation ---
        vol_confirmed = vol_ratio >= self.sc.volume_multiplier

        # --- Determine direction ---
        if ema_bullish and rsi_ok_long:
            direction = Direction.LONG
        elif ema_bearish and rsi_ok_short:
            direction = Direction.SHORT
        else:
            logger.info("No clear direction: ema_bull=%s rsi=%.1f", ema_bullish, current_rsi)
            return None

        # --- Confidence score (0–1) ---
        score = 0.0
        score += 0.30  # base for EMA alignment
        if crossed_up and direction == Direction.LONG:
            score += 0.20
        if crossed_down and direction == Direction.SHORT:
            score += 0.20
        if current_adx >= self.sc.adx_threshold:
            score += 0.25
        elif current_adx >= self.sc.min_adx:
            score += 0.10
        if vol_confirmed:
            score += 0.15
        if direction == Direction.LONG and current_rsi < 60:
            score += 0.10
        if direction == Direction.SHORT and current_rsi > 40:
            score += 0.10
        score = min(score, 1.0)

        # --- SL and TP ---
        sl_distance = current_atr * self.tc.sl_atr_multiplier
        tp_distance = current_atr * self.tc.tp_atr_multiplier

        if direction == Direction.LONG:
            # SL: below swing low or ATR-based, whichever is tighter (more protective)
            structure_sl = swings["swing_low"] - current_atr * 0.3
            atr_sl = price - sl_distance
            sl_price = max(structure_sl, atr_sl)  # higher = tighter for long
            tp_price = price + tp_distance
        else:
            structure_sl = swings["swing_high"] + current_atr * 0.3
            atr_sl = price + sl_distance
            sl_price = min(structure_sl, atr_sl)  # lower = tighter for short
            tp_price = price - tp_distance

        # --- R:R check ---
        risk = abs(price - sl_price)
        reward = abs(tp_price - price)
        if risk <= 0:
            return None
        rr = reward / risk

        if rr < self.tc.min_risk_reward:
            logger.info(
                "R:R %.2f below minimum %.2f — skipping signal",
                rr, self.tc.min_risk_reward
            )
            return None

        reason_parts = [
            f"EMA{'cross' if (crossed_up or crossed_down) else ' aligned'}",
            f"ADX={current_adx:.1f}",
            f"RSI={current_rsi:.1f}",
            f"Vol={vol_ratio:.2f}x",
            f"R:R={rr:.2f}",
        ]

        return Signal(
            direction=direction,
            confidence=score,
            atr=current_atr,
            entry_price=price,
            sl_price=round(sl_price, 2),
            tp_price=round(tp_price, 2),
            risk_reward=rr,
            adx=current_adx,
            rsi=current_rsi,
            reason=", ".join(reason_parts),
        )
