"""
Strategia del RETTANGOLO - BREAKOUT + RETEST.
Logica (da video 'Diari di Trading' + conferma Mattia):
1. Range = high/low candela daily precedente (ieri, 24h)
2. MAI LONG sul TOP del range (li' ci sono venditori)
3. MAI SHORT sul BOTTOM del range (li' ci sono compratori)
4. MAI tradare nel MID del range (zona caotica)
5. Aspetta che una candela intraday chiuda OLTRE il range (breakout)
6. Aspetta che la candela successiva torni a toccare il livello rotto (retest)
7. Sul retest: pattern 2 candele (prima opposta al segnale, poi Doji/Hammer nella direzione del segnale)
8. ENTRY: limite al TOP (per SHORT) o al BOTTOM (per LONG)
9. SL: leggermente sopra il max candela Hammer (per SHORT) / sotto il min (per LONG)
10. TP: MID range (opzionale: min/max corrente per trailing)
"""
from typing import List, Dict, Optional


def is_doji(k: Dict, body_ratio: float = 0.1) -> bool:
    body = abs(k["close"] - k["open"])
    rng = k["high"] - k["low"]
    if rng == 0:
        return False
    return body / rng <= body_ratio


def has_hammer_bullish(k: Dict, body_ratio: float = 0.3) -> bool:
    body = abs(k["close"] - k["open"])
    rng = k["high"] - k["low"]
    if rng == 0:
        return False
    lower_shadow = min(k["open"], k["close"]) - k["low"]
    return (lower_shadow >= 2 * body) and (body / rng <= body_ratio) and k["close"] >= k["open"]


def has_hammer_bearish(k: Dict, body_ratio: float = 0.3) -> bool:
    body = abs(k["close"] - k["open"])
    rng = k["high"] - k["low"]
    if rng == 0:
        return False
    upper_shadow = k["high"] - max(k["open"], k["close"])
    return (upper_shadow >= 2 * body) and (body / rng <= body_ratio) and k["close"] <= k["open"]


def _is_two_candle_pattern(cur: Dict, prev: Dict, side: str) -> bool:
    """Pattern 2 candele del video 'Diari di Trading':
    - SHORT: candela verde (oppure rialzista) + candela rossa con Doji/Hammer ribassista
    - LONG:  candela rossa (oppure ribassista) + candela verde con Doji/Hammer rialzista
    Returns: True se il pattern e' confermato.
    """
    if side == "SHORT":
        # prima candela: rialzista (verde) o rialzista contenuta (close >= open)
        prev_bullish = prev["close"] >= prev["open"]
        # seconda candela: rossa (close < open) + Doji o Hammer ribassista
        cur_bearish = cur["close"] < cur["open"]
        cur_pattern = is_doji(cur) or has_hammer_bearish(cur) or cur_bearish
        return prev_bullish and cur_pattern
    else:  # LONG
        # prima candela: ribassista (close < open)
        prev_bearish = prev["close"] < prev["open"]
        # seconda candela: verde (close >= open) + Doji o Hammer rialzista
        cur_bullish = cur["close"] >= cur["open"]
        cur_pattern = is_doji(cur) or has_hammer_bullish(cur) or cur_bullish
        return prev_bearish and cur_pattern


def compute_signal(prev_daily: Dict, intraday_history: List[Dict], idx: int,
                   sl_buffer_pct: float = 0.01, require_two_candles: bool = True) -> Optional[Dict]:
    """
    Calcola il segnale per la candela intraday corrente (idx in intraday_history).
    prev_daily: candela daily precedente (range di riferimento)
    intraday_history: lista di candele intraday (es. 2H) ordinate cronologicamente
    idx: indice della candela corrente da valutare
    sl_buffer_pct: buffer SL sopra/sotto max/min candela Hammer (default 0.5% se None)
    require_two_candles: True = pattern 2 candele (default video), False = 1 candela (legacy)

    Ritorna None se nessun segnale, altrimenti dict con {signal, sl, tp, entry, reason, sl_ref}.
    """
    if idx < 1 or idx >= len(intraday_history):
        return None
    rng_top = prev_daily["high"]
    rng_bot = prev_daily["low"]
    rng_mid = (rng_top + rng_bot) / 2
    rng_size = rng_top - rng_bot
    if rng_size <= 0:
        return None
    # MAI LONG sul TOP, MAI SHORT sul BOTTOM (gia' implicito nella logica breakout+retest)
    # cerca breakout nelle ultime N candele PRIMA di idx
    lookback = min(10, idx)
    breakout_dir = None
    breakout_idx = None
    for j in range(idx - lookback, idx):
        bar = intraday_history[j]
        if bar["close"] > rng_top:
            breakout_dir = "up"
            breakout_idx = j
            break
        if bar["close"] < rng_bot:
            breakout_dir = "down"
            breakout_idx = j
            break
    if breakout_dir is None:
        return None
    # candela corrente: deve toccare il livello rotto (retest)
    cur = intraday_history[idx]
    prev_bar = intraday_history[idx - 1]
    if breakout_dir == "up":
        # retest del TOP da sopra (prezzo scende a toccare TOP)
        if cur["low"] <= rng_top and cur["close"] <= rng_top:
            # pattern inversione ribassista
            pattern_ok = False
            if require_two_candles:
                # Pattern 2 candele del video: verde (precedente) + rossa/Hammer (corrente)
                pattern_ok = _is_two_candle_pattern(cur, prev_bar, "SHORT")
            else:
                # legacy 1 candela
                pattern_ok = is_doji(cur) or has_hammer_bearish(cur) or cur["close"] < cur["open"]
            if pattern_ok:
                entry = rng_top
                # SL: leggermente sopra il max della candela corrente (Hammer) — video
                sl_ref = cur["high"]
                sl = sl_ref * (1 + sl_buffer_pct)
                # TP: MID range (priorita') oppure minimo corrente (opzionale trailing)
                tp = rng_mid
                return {
                    "signal": "SHORT",
                    "entry": entry,
                    "sl": round(sl, 6),
                    "sl_ref": round(sl_ref, 6),  # max candela Hammer (per trailing)
                    "tp": round(tp, 6),
                    "tp_alt": round(cur["low"], 6),  # min corrente (opzionale trailing)
                    "reason": "breakout UP @ ts=" + str(intraday_history[breakout_idx]["ts"]) +
                              ", retest su TOP, pattern " + ("2 candele" if require_two_candles else "1 candela")
                }
    else:  # breakout down
        # retest del BOTTOM da sotto (prezzo sale a toccare BOTTOM)
        if cur["high"] >= rng_bot and cur["close"] >= rng_bot:
            pattern_ok = False
            if require_two_candles:
                pattern_ok = _is_two_candle_pattern(cur, prev_bar, "LONG")
            else:
                pattern_ok = is_doji(cur) or has_hammer_bullish(cur) or cur["close"] > cur["open"]
            if pattern_ok:
                entry = rng_bot
                # SL: leggermente sotto il min della candela corrente (Hammer) — video
                sl_ref = cur["low"]
                sl = sl_ref * (1 - sl_buffer_pct)
                tp = rng_mid
                return {
                    "signal": "LONG",
                    "entry": entry,
                    "sl": round(sl, 6),
                    "sl_ref": round(sl_ref, 6),  # min candela Hammer (per trailing)
                    "tp": round(tp, 6),
                    "tp_alt": round(cur["high"], 6),  # max corrente (opzionale trailing)
                    "reason": "breakout DOWN @ ts=" + str(intraday_history[breakout_idx]["ts"]) +
                              ", retest su BOTTOM, pattern " + ("2 candele" if require_two_candles else "1 candela")
                }
    return None


def scan_signals(prev_daily: Dict, intraday_history: List[Dict], sl_buffer_pct: float = 0.01) -> list:
    """Scansiona tutte le candele intraday e ritorna la lista di segnali storici."""
    signals = []
    for i in range(1, len(intraday_history)):
        sig = compute_signal(prev_daily, intraday_history, i, sl_buffer_pct)
        if sig:
            sig["date"] = intraday_history[i]["date"]
            sig["entry"] = sig["entry"]  # entry price = TOP/BOTTOM
            signals.append(sig)
    return signals


def backtest(klines_daily: List[Dict], klines_intraday: List[Dict],
             sl_buffer_pct: float = 0.01) -> List[Dict]:
    """
    Backtest della strategia: per ogni candela daily 'ieri', calcola segnali
    sulle candele intraday fino alla prossima daily.
    """
    trades = []
    # mappa: per ogni candela daily, identifica l'intervallo intraday successivo
    for d in range(1, len(klines_daily)):
        prev_day = klines_daily[d - 1]
        cur_day = klines_daily[d]
        # filtra intraday: solo candele il giorno 'cur_day' e fino alla sua chiusura
        day_start = cur_day["ts"]
        day_end = day_start + 24 * 3600 * 1000
        day_intra = [k for k in klines_intraday if day_start <= k["ts"] < day_end]
        if len(day_intra) < 2:
            continue
        # mappa indice in intraday_history completo
        # Per semplicità, uso l'indice locale (in day_intra)
        for i in range(1, len(day_intra)):
            sig = compute_signal(prev_day, day_intra, i, sl_buffer_pct)
            if not sig:
                continue
            entry_bar = day_intra[i + 1] if i + 1 < len(day_intra) else day_intra[i]
            entry = sig["entry"]  # limit al TOP/BOTTOM
            sl = sig["sl"]
            tp = sig["tp"]
            outcome = "OPEN"
            exit_price = day_intra[-1]["close"]
            for j in range(i + 1, len(day_intra)):
                bar = day_intra[j]
                if sig["signal"] == "LONG":
                    if bar["low"] <= sl:
                        outcome = "SL"; exit_price = sl; break
                    if bar["high"] >= tp:
                        outcome = "TP"; exit_price = tp; break
                else:
                    if bar["high"] >= sl:
                        outcome = "SL"; exit_price = sl; break
                    if bar["low"] <= tp:
                        outcome = "TP"; exit_price = tp; break
            if sig["signal"] == "LONG":
                pnl_pct = (exit_price - entry) / entry * 100
            else:
                pnl_pct = (entry - exit_price) / entry * 100
            trades.append({
                "date": day_intra[i]["date"],
                "signal": sig["signal"],
                "entry": entry,
                "sl": sl,
                "tp": tp,
                "outcome": outcome,
                "exit": exit_price,
                "pnl_pct": round(pnl_pct, 2),
            })
    return trades
