"""Signal engine: 8 weighted factors → BUY / SELL / NEUTRAL with a confidence %.
Factors: Supertrend, RSI zone, EMA trend, MACD histogram,
         Volume-with-direction, Stochastic K/D, Bollinger squeeze, VWAP side.
Multi-timeframe confirmation via higher_tfs resampling.
"""
from dataclasses import dataclass, field, asdict
from . import indicators as ind


@dataclass
class Signal:
    side: str = "NEUTRAL"
    confidence: int = 0
    price: float = 0.0
    atr: float = 0.0
    adx: float = 0.0
    adx_plus: float = 0.0
    adx_minus: float = 0.0
    rsi: float = 50.0
    stoch_k: float = 50.0
    stoch_d: float = 50.0
    bb_pct: float = 0.5
    bb_bw: float = 0.0
    vwap: float = 0.0
    factors: dict = field(default_factory=dict)
    note: str = ""

    def to_dict(self):
        return asdict(self)


def resample(df, rule):
    return df.resample(rule).agg(
        {"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"}
    ).dropna()


# Factor weights – higher means more important
_WEIGHTS = {
    "supertrend": 2.0,
    "rsi":        1.5,
    "ema":        1.5,
    "macd":       1.0,
    "volume":     1.0,
    "stoch":      1.5,
    "bb_squeeze": 1.0,
    "vwap":       1.5,
}
_TOTAL_WEIGHT = sum(_WEIGHTS.values())


def _weighted_confidence(votes: dict, weights: dict) -> int:
    """Return 0-100 based on weighted votes (True/False per factor)."""
    score = sum(weights.get(k, 1.0) for k, v in votes.items() if v)
    return int(score / _TOTAL_WEIGHT * 100)


def analyze(df, p) -> Signal:
    if len(df) < max(60, p["ema_trend"] + 5):
        return Signal(note="not enough data")

    c = df.close

    # ── indicators ─────────────────────────────────────────────────────────
    st, _ = ind.supertrend(df, p["supertrend_period"], p["supertrend_mult"])
    rv = ind.rsi(c, p["rsi_period"])
    _, _, hist = ind.macd(c)
    e = ind.ema(c, p["ema_trend"])
    a = ind.atr(df, 14)
    ad, pdi, mdi = ind.adx(df, 14)
    sk, sd = ind.stochastic(df)
    _, _, _, pct_b, bw = ind.bollinger(c, 20, 2.0)
    vw = ind.vwap(df)

    vol_ma = df.volume.rolling(p["vol_ma"]).mean()

    price   = float(c.iloc[-1])
    rsi_v   = float(rv.iloc[-1])
    hist_v  = float(hist.iloc[-1])
    ema_v   = float(e.iloc[-1])
    sk_v    = float(sk.iloc[-1])
    sd_v    = float(sd.iloc[-1])
    pct_bv  = float(pct_b.iloc[-1])
    bw_v    = float(bw.iloc[-1])
    vwap_v  = float(vw.iloc[-1]) if not vw.isna().all() else price
    adx_v   = float(ad.iloc[-1])
    pdi_v   = float(pdi.iloc[-1])
    mdi_v   = float(mdi.iloc[-1])
    atr_v   = float(a.iloc[-1])
    green   = bool(c.iloc[-1] > df.open.iloc[-1])
    vol_ok  = bool(df.volume.iloc[-1] > vol_ma.iloc[-1])

    # Bollinger squeeze = bandwidth < rolling 6-bar minimum → low volatility breakout zone
    bw_squeeze = bool(bw_v < bw.rolling(6).min().iloc[-1] * 1.05)

    bull = {
        "supertrend": bool(st.iloc[-1] == 1),
        "rsi":        bool(50 < rsi_v < p["rsi_ob"]),
        "ema":        bool(price > ema_v),
        "macd":       bool(hist_v > 0),
        "volume":     vol_ok and green,
        "stoch":      bool(sk_v > sd_v and sk_v < 80),
        "bb_squeeze": bw_squeeze and price > ema_v,
        "vwap":       bool(price > vwap_v),
    }
    bear = {
        "supertrend": bool(st.iloc[-1] == -1),
        "rsi":        bool(p["rsi_os"] < rsi_v < 50),
        "ema":        bool(price < ema_v),
        "macd":       bool(hist_v < 0),
        "volume":     vol_ok and not green,
        "stoch":      bool(sk_v < sd_v and sk_v > 20),
        "bb_squeeze": bw_squeeze and price < ema_v,
        "vwap":       bool(price < vwap_v),
    }

    bull_conf = _weighted_confidence(bull, _WEIGHTS)
    bear_conf = _weighted_confidence(bear, _WEIGHTS)
    conf = max(bull_conf, bear_conf)
    side = "NEUTRAL"
    if bull_conf > bear_conf and bull_conf >= p["min_confidence"]:
        side = "BUY"
    elif bear_conf > bull_conf and bear_conf >= p["min_confidence"]:
        side = "SELL"

    sig = Signal(
        side=side, confidence=conf, price=price,
        atr=atr_v, adx=adx_v, adx_plus=pdi_v, adx_minus=mdi_v,
        rsi=rsi_v, stoch_k=sk_v, stoch_d=sd_v,
        bb_pct=pct_bv, bb_bw=bw_v, vwap=vwap_v,
        factors={"bull": bull, "bear": bear, "mtf": {}},
    )
    if side != "NEUTRAL" and p.get("min_adx", 0) and sig.adx < p["min_adx"]:
        sig.side, sig.note = "NEUTRAL", f"ADX {sig.adx:.0f} below {p['min_adx']}"
    return sig


def analyze_mtf(df, p) -> Signal:
    s = analyze(df, p)
    if "mtf" not in s.factors:
        return s
    for rule in p.get("higher_tfs", []):
        h = resample(df, rule)
        if len(h) < 20:
            continue
        d, _ = ind.supertrend(h, p["supertrend_period"], p["supertrend_mult"])
        # Also get RSI for htf for richer dashboard display
        r_htf = ind.rsi(h.close, p["rsi_period"])
        htf_side = "BUY" if d.iloc[-1] == 1 else "SELL"
        s.factors["mtf"][rule] = {
            "side": htf_side,
            "rsi": round(float(r_htf.iloc[-1]), 1),
        }
    if s.side != "NEUTRAL" and p.get("mtf_confirm", True):
        for rule, v in s.factors["mtf"].items():
            htf = v["side"] if isinstance(v, dict) else v
            if htf != s.side:
                s.side, s.note = "NEUTRAL", f"{rule} timeframe disagrees"
                break
    if s.side != "NEUTRAL" and s.side not in p.get("sides", ["BUY", "SELL"]):
        s.side, s.note = "NEUTRAL", "this side is switched off"
    return s
