"""REGRESSION-LOCK COMUNE dei gate PORT06 live (exit16 / trendmax / dip01).

Queste funzioni erano copiate quasi-verbatim in exit16_port06_impact.py,
trendmax_port06_impact.py e dip01_exit16_impact.py. Sono il regression-lock
delle DECISIONI LIVE (EXIT-16, swap hurst->trend, DIP01 EXIT-16): la copy-drift
fra le copie avrebbe corrotto i verdetti, quindi vivono qui in un'unica copia.

NON cambiare la matematica: i gate devono restare riproducibili byte-a-byte.
Se un nuovo gate richiede un comportamento diverso, PARAMETRIZZARE (come fu
fatto per hurst_mask/trend_max), mai biforcare una copia.

Contenuto:
  build_trades_variant : replay ESATTO di risk_management.build_trades sulle
                         fade (mode="orig" == canonico), con i rami varianti
                         EXIT-16 (mode="exit16"), filtro trend (trend_max) e
                         loss-guard Hurst (hurst_mask) parametrici.
  equity_from_trades   : trade -> equity giornaliera normalizzata su IDX
                         (stesso flusso di combine_portfolio.fade_daily_equity).
  port_metrics         : metriche FULL/OOS del portafoglio con la STESSA
                         matematica pesi di Portfolio.backtest (weight_vector
                         su tutti gli sleeve, ribilancio come port_returns).
  dd                   : max drawdown % di una equity.

NB: l'engine DIP01 (dip_trades in dip01_exit16_impact.py) NON e' una copia di
build_trades_variant ma un sibling deliberatamente diverso (long-only, mode
"orig_gap" gap-aware, j clampato a n-1 a fine serie, niente filtri trend/hurst)
-> resta nel suo script.
"""
from __future__ import annotations

import sys
from pathlib import Path

import numpy as np
import pandas as pd

PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))

from scripts.analysis.strategy_research import atr
from scripts.analysis.risk_management import FEE_RT, LEV, POS, INIT
from scripts.analysis.combine_portfolio import (
    _norm, IDX, port_returns, metrics, SPLIT,
)
from src.portfolio import weighting as W

BUFFER = 0.5        # EXIT-16 close-confirm (come in produzione)
EMA_LONG = 200


def build_trades_variant(ents, df, mode, trend_max, hurst_mask=None,
                         buffer=BUFFER, lev=LEV, fee_rt=FEE_RT, ema_long=EMA_LONG):
    """Replica ESATTA di risk_management.build_trades, con i rami varianti.

    mode="orig"   : SL intrabar al livello (SL prima del TP) == canonico.
    mode="exit16" : SL intrabar OFF; TP intrabar al livello (priorita' nel bar);
                    SL solo se il CLOSE sfonda sl0 -/+ buffer*ATR14[j], fill a close[j].
    trend_max     : None = filtro OFF; 3.0 = config live.
    hurst_mask    : bool[i]=True -> salta l'ingresso (loss-guard storico).
    """
    h, l, c = df["high"].values, df["low"].values, df["close"].values
    n = len(c)
    a = atr(df, 14)
    el = pd.Series(c).ewm(span=ema_long, adjust=False).mean().values
    fee = fee_rt * lev
    out = []
    last = -1
    for e in ents:
        i, d = e["i"], e["d"]
        if i <= last or i + 1 >= n:
            continue
        if hurst_mask is not None and hurst_mask[i]:
            continue
        if trend_max is not None and a[i] and abs(c[i] - el[i]) / a[i] > trend_max:
            continue
        entry = c[i]
        tp, sl0, mb = e["tp"], e["sl"], e["max_bars"]
        exit_p = c[min(i + mb, n - 1)]
        j = min(i + mb, n - 1)
        for k in range(1, mb + 1):
            j = i + k
            if j >= n:
                exit_p = c[n - 1]
                break
            if mode == "orig":
                hs = (d == 1 and l[j] <= sl0) or (d == -1 and h[j] >= sl0)
                ht = (d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp)
                if hs:
                    exit_p = sl0
                    break
                if ht:
                    exit_p = tp
                    break
                if k == mb:
                    exit_p = c[j]
            else:  # exit16
                ht = (d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp)
                if ht:
                    exit_p = tp
                    break
                aj = a[j] if np.isfinite(a[j]) else 0.0
                confirm = (d == 1 and c[j] < sl0 - buffer * aj) or \
                          (d == -1 and c[j] > sl0 + buffer * aj)
                if confirm:
                    exit_p = c[j]
                    break
                if k == mb:
                    exit_p = c[j]
        ret = (exit_p - entry) / entry * d * lev - fee
        out.append((i, j, ret))
        last = j
    return out


def equity_from_trades(df, trades):
    """Trade -> equity giornaliera su IDX (flusso di combine_portfolio.fade_daily_equity)."""
    ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
    n = len(df)
    eq = np.full(n, INIT, dtype=float)
    cap = INIT
    for i, j, ret in sorted(trades, key=lambda t: t[1]):
        cap = max(cap + cap * POS * ret, 10.0)
        eq[j:] = cap
    s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill()
    return _norm(s)


def port_metrics(members: dict[str, pd.Series], p):
    """Metriche (FULL, OOS) del portafoglio p con la STESSA matematica pesi cap
    di Portfolio.backtest."""
    ids = p.sleeve_ids
    dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids})
    w = W.weight_vector(p.weighting, ids, dr, weights=p.weights,
                        caps=p.caps, clusters=p.clusters, lookback=p.vol_lookback)
    drp = port_returns({i: members[i] for i in ids}, w)
    return metrics(drp), metrics(drp, lo=SPLIT)


def dd(s):
    """Max drawdown % di una serie equity."""
    pk = s.cummax()
    return float(((pk - s) / pk).max() * 100)
