# ═══════════════════════════════════════════════════════════════
#  strategy.py — Análise Técnica e Geração de Sinais
#  Indicadores: RSI · MACD · Bollinger Bands · EMA · Volume
# ═══════════════════════════════════════════════════════════════

import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import Optional
import config as cfg


@dataclass
class Sinal:
    acao:       str        # "COMPRA" | "VENDA" | "AGUARDAR"
    score:      int        # 0–10
    confianca:  float      # 0–100%
    preco:      float
    stop:       float
    alvo1:      float
    alvo2:      float
    rr:         float      # Risk:Reward
    rsi:        float
    macd_hist:  float
    tendencia:  str        # "ALTA" | "BAIXA" | "LATERAL"
    motivo:     str
    alertas:    list


class AnaliseTecnica:
    """Calcula todos os indicadores técnicos a partir de candles OHLCV."""

    def __init__(self, df: pd.DataFrame):
        """
        df deve ter colunas: timestamp, open, high, low, close, volume
        """
        self.df = df.copy()
        self._calcular_indicadores()

    def _calcular_indicadores(self):
        c = self.df["close"]
        v = self.df["volume"]

        # ── RSI ──────────────────────────────────────────────
        delta = c.diff()
        gain  = delta.clip(lower=0)
        loss  = (-delta).clip(lower=0)
        avg_g = gain.ewm(span=cfg.RSI_PERIOD, adjust=False).mean()
        avg_l = loss.ewm(span=cfg.RSI_PERIOD, adjust=False).mean()
        rs    = avg_g / avg_l.replace(0, 1e-10)
        self.df["rsi"] = 100 - (100 / (1 + rs))

        # ── MACD ─────────────────────────────────────────────
        ema_fast         = c.ewm(span=cfg.MACD_FAST, adjust=False).mean()
        ema_slow         = c.ewm(span=cfg.MACD_SLOW, adjust=False).mean()
        self.df["macd"]  = ema_fast - ema_slow
        self.df["signal"]= self.df["macd"].ewm(span=cfg.MACD_SIGNAL, adjust=False).mean()
        self.df["hist"]  = self.df["macd"] - self.df["signal"]

        # ── BOLLINGER BANDS ──────────────────────────────────
        self.df["bb_mid"]   = c.rolling(cfg.BB_PERIOD).mean()
        bb_std              = c.rolling(cfg.BB_PERIOD).std()
        self.df["bb_upper"] = self.df["bb_mid"] + cfg.BB_STD * bb_std
        self.df["bb_lower"] = self.df["bb_mid"] - cfg.BB_STD * bb_std
        self.df["bb_width"] = (self.df["bb_upper"] - self.df["bb_lower"]) / self.df["bb_mid"] * 100

        # ── EMAs ─────────────────────────────────────────────
        self.df["ema_fast"]  = c.ewm(span=cfg.EMA_FAST,  adjust=False).mean()
        self.df["ema_slow"]  = c.ewm(span=cfg.EMA_SLOW,  adjust=False).mean()
        self.df["ema_trend"] = c.ewm(span=cfg.EMA_TREND, adjust=False).mean()

        # ── VOLUME ───────────────────────────────────────────
        self.df["vol_ma"]    = v.rolling(20).mean()
        self.df["vol_ratio"] = v / self.df["vol_ma"].replace(0, 1)

        # ── ATR (Average True Range) ─────────────────────────
        h, l, pc = self.df["high"], self.df["low"], c.shift(1)
        tr = pd.concat([h - l, (h - pc).abs(), (l - pc).abs()], axis=1).max(axis=1)
        self.df["atr"] = tr.rolling(14).mean()

    @property
    def ultimo(self) -> pd.Series:
        return self.df.iloc[-1]

    @property
    def penultimo(self) -> pd.Series:
        return self.df.iloc[-2]

    def gerar_sinal(self) -> Sinal:
        u  = self.ultimo
        p  = self.penultimo
        price = u["close"]
        alertas = []
        motivos = []
        score   = 0

        # ═══════════════════════════════════════════════════
        #  PONTUAÇÃO DE COMPRA (+) / VENDA (-)
        # ═══════════════════════════════════════════════════

        # 1. RSI
        rsi = u["rsi"]
        if rsi < cfg.RSI_OVERSOLD:
            score += 3; motivos.append(f"RSI={rsi:.1f} oversold")
        elif rsi < 45:
            score += 1; motivos.append(f"RSI={rsi:.1f} neutro-bull")
        elif rsi > cfg.RSI_OVERBOUGHT:
            score -= 3; motivos.append(f"RSI={rsi:.1f} overbought")
        elif rsi > 55:
            score -= 1

        # 2. MACD Histograma
        hist_atual  = u["hist"]
        hist_prev   = p["hist"]
        if hist_atual > 0 and hist_prev <= 0:
            score += 3; motivos.append("MACD cruzou para CIMA ✅")
        elif hist_atual > 0:
            score += 1; motivos.append("MACD histograma positivo")
        elif hist_atual < 0 and hist_prev >= 0:
            score -= 3; motivos.append("MACD cruzou para BAIXO ❌")
        elif hist_atual < 0:
            score -= 1

        # 3. Bollinger Bands
        if price <= u["bb_lower"]:
            score += 3; motivos.append("Preço na banda inferior BB 🟢")
        elif price <= u["bb_lower"] * 1.005:
            score += 1; motivos.append("Próximo banda inferior BB")
        elif price >= u["bb_upper"]:
            score -= 3; motivos.append("Preço na banda superior BB 🔴")
        elif price >= u["bb_upper"] * 0.995:
            score -= 1

        # 4. EMAs — tendência
        acima_ema_fast  = price > u["ema_fast"]
        acima_ema_slow  = price > u["ema_slow"]
        acima_ema_trend = price > u["ema_trend"]
        golden_cross    = u["ema_fast"] > u["ema_slow"] and p["ema_fast"] <= p["ema_slow"]
        death_cross     = u["ema_fast"] < u["ema_slow"] and p["ema_fast"] >= p["ema_slow"]

        if golden_cross:
            score += 3; motivos.append("Golden Cross EMA ✨")
        elif acima_ema_fast and acima_ema_slow:
            score += 2; motivos.append("Acima EMA rápida e lenta")
        elif acima_ema_fast:
            score += 1

        if death_cross:
            score -= 3; motivos.append("Death Cross EMA ⚠️")
        elif not acima_ema_fast and not acima_ema_slow:
            score -= 2; motivos.append("Abaixo de ambas as EMAs")

        # 5. Volume
        vol_ratio = u["vol_ratio"]
        if vol_ratio >= cfg.MIN_VOLUME_RATIO:
            score += 1; motivos.append(f"Volume {vol_ratio:.1f}x acima da média")
        elif vol_ratio < 0.7:
            alertas.append(f"Volume baixo ({vol_ratio:.1f}x) — sinal fraco")

        # 5. BB Squeeze — volatilidade comprimida
        if u["bb_width"] < 2.0:
            alertas.append("BB Squeeze detectado — breakout iminente")

        # ═══════════════════════════════════════════════════
        #  TENDÊNCIA
        # ═══════════════════════════════════════════════════
        if acima_ema_fast and acima_ema_slow and acima_ema_trend:
            tendencia = "ALTA"
        elif not acima_ema_fast and not acima_ema_slow and not acima_ema_trend:
            tendencia = "BAIXA"
        else:
            tendencia = "LATERAL"

        # ═══════════════════════════════════════════════════
        #  DECISÃO FINAL
        # ═══════════════════════════════════════════════════
        atr = u["atr"] if not np.isnan(u["atr"]) else price * 0.01

        if score >= cfg.MIN_SCORE:
            acao  = "COMPRA"
            stop  = price - (atr * 1.5)
            alvo1 = price + (atr * 2.0)
            alvo2 = price + (atr * 4.0)
        elif score <= -cfg.MIN_SCORE:
            acao  = "VENDA"
            stop  = price + (atr * 1.5)
            alvo1 = price - (atr * 2.0)
            alvo2 = price - (atr * 4.0)
        else:
            acao  = "AGUARDAR"
            stop  = price - (atr * 1.5)
            alvo1 = price + (atr * 2.0)
            alvo2 = price + (atr * 4.0)

        risco   = abs(price - stop)
        retorno = abs(alvo1 - price)
        rr      = round(retorno / risco, 2) if risco > 0 else 0
        confianca = min(100, abs(score) / 10 * 100)

        # Alertas extras
        if rr < 1.5:
            alertas.append(f"R:R baixo ({rr}) — considere aguardar melhor entrada")
        if tendencia == "BAIXA" and acao == "COMPRA":
            alertas.append("Compra CONTRA tendência — risco maior")

        return Sinal(
            acao      = acao,
            score     = score,
            confianca = confianca,
            preco     = price,
            stop      = stop,
            alvo1     = alvo1,
            alvo2     = alvo2,
            rr        = rr,
            rsi       = rsi,
            macd_hist = hist_atual,
            tendencia = tendencia,
            motivo    = " | ".join(motivos[:4]) if motivos else "Sem confluência suficiente",
            alertas   = alertas,
        )

    def resumo(self) -> dict:
        u = self.ultimo
        return {
            "preco":     round(u["close"], 2),
            "rsi":       round(u["rsi"], 2),
            "macd":      round(u["macd"], 2),
            "hist":      round(u["hist"], 2),
            "bb_upper":  round(u["bb_upper"], 2),
            "bb_mid":    round(u["bb_mid"], 2),
            "bb_lower":  round(u["bb_lower"], 2),
            "bb_width":  round(u["bb_width"], 2),
            "ema_fast":  round(u["ema_fast"], 2),
            "ema_slow":  round(u["ema_slow"], 2),
            "ema_trend": round(u["ema_trend"], 2),
            "vol_ratio": round(u["vol_ratio"], 2),
            "atr":       round(u["atr"], 2),
        }
