"""Gate feed CONGELATO (2026-06-15): salta entry+exit degli sleeve concentrati
(single/ml/pairs) il cui feed di DECISIONE 1h e' fermo (guasto, es. ETH-PERPETUAL
a 1661.95 per 36h+). Distingue il GUASTO (close mai cambiata per decine di barre)
dall'ILLIQUIDITA' (SOL/LTC/ADA: molte barre flat ma il prezzo si muove -> run corte).
Auto-guarente: la barra di ripresa e' non-flat -> il gate si rilascia da solo."""
import pandas as pd
from src.portfolio.runner import _frozen_assets, _feed_gated_sids


def _df(completed, forming):
    """Serie 1h: barre complete (flat = O=H=L=C; non-flat quando il prezzo cambia)
    + una candela in formazione (ultima riga, esclusa dal detector)."""
    end_ms = int(pd.Timestamp.now(tz="UTC").floor("h").timestamp() * 1000)
    vals = list(completed) + [forming]
    n = len(vals)
    rows = []
    for j, v in enumerate(vals):
        ts = end_ms - (n - 1 - j) * 3_600_000
        rows.append({"timestamp": ts, "open": v, "high": v, "low": v, "close": v, "volume": 0.0})
    for j in range(1, n):                          # bar NON-flat quando il prezzo si muove
        if rows[j]["close"] != rows[j - 1]["close"]:
            rows[j]["high"] = rows[j]["close"] * 1.001
    return pd.DataFrame(rows)


def test_frozen_feed_detected():
    raw = {"ETH": _df([1661.95] * 40, 1661.95)}    # prezzo MAI cambiato (guasto)
    assert _frozen_assets(raw, 12) == {"ETH"}


def test_live_feed_not_frozen():
    raw = {"BTC": _df([65000 + i * 5 for i in range(40)], 65200.0)}  # sempre in movimento
    assert _frozen_assets(raw, 12) == set()


def test_illiquid_but_alive_not_frozen():
    # ultima barra completa FLAT ma il prezzo si e' mosso da poche barre (run < soglia):
    # alt illiquido (SOL/LTC/ADA), NON un guasto -> non gateato.
    raw = {"LTC": _df([44.0] * 30 + [45.0] * 5, 45.0)}
    assert _frozen_assets(raw, 12) == set()


def test_resume_releases_gate():
    # rilascio solo su una barra COMPLETATA non-flat (la forming non basta -> coerente
    # con la valutazione su barre settled, EXIT-16). NON e' l'entry-guard post-flat: la
    # barra di ripresa NON viene saltata, il tick riprende su di essa.
    raw = {"ETH": _df([1661.95] * 40 + [1700.0], 1700.0)}
    assert _frozen_assets(raw, 12) == set()
    # finche' la ripresa e' solo nella candela in formazione, resta congelato
    assert _frozen_assets({"ETH": _df([1661.95] * 40, 1700.0)}, 12) == {"ETH"}


def test_threshold_zero_disables():
    raw = {"ETH": _df([1661.95] * 40, 1661.95)}
    assert _frozen_assets(raw, 0) == set()


def test_gated_scope_matches_eth_legs():
    """Sui veri sleeve PORT06: con ETH congelato si gatano TUTTE e sole le gambe ETH
    (3 fade ETH + SH_ETH + 5 pairs con gamba ETH). BTC-only, BTC/LTC e i multi-asset
    (anche ROT02 che ha ETH nell'universo) restano attivi."""
    from scripts.portfolios._defs import FADE, PAIRS, SHAPE, HONEST
    specs = FADE + PAIRS + SHAPE + HONEST
    g = _feed_gated_sids(specs, {"ETH"})
    assert {"MR01_ETH", "MR02_ETH", "MR07_ETH", "SH_ETH",
            "PR_ETHBTC", "PR_ETHBTC_15M", "PR_LTCETH", "PR_ADAETH", "PR_ETHSOL"} <= g
    assert g.isdisjoint({"MR01_BTC", "MR02_BTC", "MR07_BTC", "SH_BTC",
                         "DIP01_BTC", "PR_BTCLTC"})
    assert g.isdisjoint({"TR01_basket", "ROT02_rot"})   # multi-asset mai gateati
    assert _feed_gated_sids(specs, set()) == set()       # nessun congelato -> nessun gate
