"""REAL-TRUTH (2026-06-10): col flag attivo il ledger `capital` dei worker eseguiti
si aggiorna col PnL dei FILL REALI (fee reali incluse); il sim resta diagnostica nel
log CLOSE (pnl_source/sim_pnl/real_pnl). Fallback dichiarato al sim solo se il trade
reale non e' mai esistito/fillato. Executor finti, nessuna rete."""
import json
from types import SimpleNamespace

from src.live.execution import Fill, PairFill
from src.live.strategy_worker import StrategyWorker
from src.live.pairs_worker import PairsWorker


# ---------------- helpers ----------------

class FakeExec:
    """Single-leg: open filla a `open_px`, close a `close_px` (fee fisse)."""
    verify_polls = 1
    verify_sleep = 0.0
    disaster_sl_pct = None

    def __init__(self, open_px=100.0, close_px=100.0, close_verified=True):
        self.open_px = open_px
        self.close_px = close_px
        self.close_verified = close_verified

    def open(self, instrument, side, notional, label=None):
        amt = round(notional / self.open_px, 6)
        return Fill(instrument, side, notional, amt, self.open_px, 0.0, 0.05,
                    "oid-open", "filled", True, filled_amount=amt)

    def place_tp_limit(self, *a, **k):
        return Fill("x", "sell", 0.0, 0.0, None, 0.0, 0.0, None, None, False)

    def place_disaster_sl(self, *a, **k):
        return Fill("x", "sell", 0.0, 0.0, None, 0.0, 0.0, None, None, False)

    def cancel_order(self, oid):
        return {"state": "cancelled"}

    def resting_fills(self, instrument, oid):
        return 0.0, None, 0.0

    def close_amount(self, instrument, side, amount, label=None):
        return Fill(instrument, "sell" if side == "buy" else "buy", 0.0, amount,
                    self.close_px, 0.0, 0.05, "oid-close", "filled", self.close_verified,
                    filled_amount=amount)


def _sw(tmp_path, fake, real_truth=True):
    w = StrategyWorker(strategy=SimpleNamespace(name="FAKE", fee_rt=0.001),
                       asset="BTC", tf="1h", capital=100.0, position_size=0.5,
                       leverage=2.0, data_dir=tmp_path, executor=fake,
                       exec_instrument="BTC_USDC-PERPETUAL", real_truth=real_truth)
    w._notify = lambda *a, **k: None
    return w


def _last_close(w):
    rows = [json.loads(l) for l in w.trades_path.read_text().strip().splitlines()]
    return [r for r in rows if r.get("event") == "CLOSE"][-1]


def _open_sim(w, price=100.0):
    from src.strategies.base import Signal
    w._open_position(Signal(idx=0, direction=1, entry_price=price,
                            metadata={"max_bars": 12}), price)


# ---------------- StrategyWorker ----------------

def test_capital_updates_from_real_fills(tmp_path):
    """Fill reali 100->105: capital += real_pnl (gross sul notional reale - fee reali),
    NON il sim pnl."""
    fake = FakeExec(open_px=100.0, close_px=105.0)
    w = _sw(tmp_path, fake)
    _open_sim(w, 100.0)
    assert w.real_in_position
    notional = w.real_entry_notional                       # = capital*ps*lev = 100
    w._close_position(105.0, "time_limit")
    real_pnl = 0.05 * notional - 0.10                      # +5% su notional - fee 2x0.05
    assert abs(w.capital - (100.0 + real_pnl)) < 1e-6
    c = _last_close(w)
    assert c["pnl_source"] == "real"
    assert abs(c["real_pnl"] - real_pnl) < 1e-3
    assert c["win"] is True and w.total_wins == 1


def test_real_loss_counts_as_loss_even_if_sim_wins(tmp_path):
    """Divergenza sim/reale: il sim vede +5% (exit sim 105) ma il fill reale e' 99
    -> in real-truth il trade e' LOSS e il capitale scende."""
    fake = FakeExec(open_px=100.0, close_px=99.0)
    w = _sw(tmp_path, fake)
    _open_sim(w, 100.0)
    w._close_position(105.0, "take_profit")               # sim esce a 105 (win sim)
    c = _last_close(w)
    assert c["pnl_source"] == "real"
    assert c["real_pnl"] < 0 and c["win"] is False
    assert w.capital < 100.0
    assert c["sim_pnl"] > 0                                # il sim avrebbe bookato un win


def test_fallback_sim_when_real_open_failed(tmp_path):
    """REAL_OPEN_FAIL (nessuna posizione reale): il ledger usa il sim, con flag."""
    class FailOpen(FakeExec):
        def open(self, instrument, side, notional, label=None):
            return Fill(instrument, side, notional, 0.0, None, 0.0, 0.0,
                        None, "error", False, notes="boom")
    w = _sw(tmp_path, FailOpen())
    _open_sim(w, 100.0)
    assert not w.real_in_position
    w._close_position(105.0, "time_limit")
    c = _last_close(w)
    assert c["pnl_source"] == "sim_fallback"
    assert c["pnl"] == c["sim_pnl"]
    assert w.capital > 100.0                               # sim pnl applicato


def test_shadow_mode_unchanged_without_flag(tmp_path):
    """real_truth=False (default storico): capital segue il SIM, real_capital il reale."""
    fake = FakeExec(open_px=100.0, close_px=99.0)
    w = _sw(tmp_path, fake, real_truth=False)
    _open_sim(w, 100.0)
    w._close_position(105.0, "take_profit")
    c = _last_close(w)
    assert "pnl_source" not in c
    assert w.capital > 100.0                               # sim win applicato al ledger
    assert w.real_capital < 100.0                          # reale in perdita, separato


# ---------------- PairsWorker ----------------

class FakePairsExec:
    def __init__(self, open_a=100.0, open_b=50.0, close_a=102.0, close_b=50.0):
        self.px = dict(open_a=open_a, open_b=open_b, close_a=close_a, close_b=close_b)

    def _fill(self, inst, side, amount, px):
        return Fill(inst, side, 0.0, amount, px, 0.0, 0.02, "oid", "filled", True,
                    filled_amount=amount)

    def open_pair(self, inst_a, inst_b, direction, notional, label=None):
        sa = "buy" if direction == 1 else "sell"
        sb = "sell" if direction == 1 else "buy"
        return PairFill(True,
                        self._fill(inst_a, sa, notional / self.px["open_a"], self.px["open_a"]),
                        self._fill(inst_b, sb, notional / self.px["open_b"], self.px["open_b"]))

    def close_pair(self, inst_a, inst_b, side_a, side_b, amount_a, amount_b, label=None):
        return PairFill(True,
                        self._fill(inst_a, "sell", amount_a, self.px["close_a"]),
                        self._fill(inst_b, "buy", amount_b, self.px["close_b"]))


def _pw(tmp_path, fake, real_truth=True):
    w = PairsWorker(asset_a="ETH", asset_b="BTC", tf="1h", capital=100.0,
                    position_size=0.2, leverage=2.0, data_dir=tmp_path,
                    executor=fake, exec_instruments={"ETH": "ETH_USDC-PERPETUAL",
                                                     "BTC": "BTC_USDC-PERPETUAL"},
                    real_truth=real_truth)
    w._notify = lambda *a, **k: None
    return w


def test_pairs_capital_updates_from_real_fills(tmp_path):
    """Long ratio, gamba A +2% reale: capital += real_pnl (2 gambe, 4 fee da 0.02)."""
    fake = FakePairsExec(open_a=100.0, close_a=102.0)
    w = _pw(tmp_path, fake)
    w._open(1, 100.0, 50.0, -2.1)
    assert w.real_in_position
    na = w.real_notional_a
    w._close(102.0, 50.0, 0.1, "mean_revert")
    real_pnl = 0.02 * na - 4 * 0.02                        # +2% gamba A - fee 4 lati
    assert abs(w.capital - (100.0 + real_pnl)) < 1e-6
    c = json.loads([l for l in w.trades_path.read_text().splitlines()
                    if '"CLOSE"' in l][-1])
    assert c["pnl_source"] == "real"
    assert w.total_trades == 1


def test_pairs_fallback_sim_on_leg_fail(tmp_path):
    """Apertura reale fallita (leg-risk unwound): chiusura su ledger sim con flag."""
    class FailOpen(FakePairsExec):
        def open_pair(self, *a, **k):
            bad = Fill("x", "buy", 0.0, 0.0, None, 0.0, 0.0, None, "error", False)
            return PairFill(False, bad, bad, unwound=False, notes="leg-fail")
    w = _pw(tmp_path, FailOpen())
    w._open(1, 100.0, 50.0, -2.1)
    assert not w.real_in_position
    w._close(102.0, 50.0, 0.1, "mean_revert")
    c = json.loads([l for l in w.trades_path.read_text().splitlines()
                    if '"CLOSE"' in l][-1])
    assert c["pnl_source"] == "sim_fallback"
    assert c["pnl"] == c["sim_pnl"]


def test_partial_close_books_only_filled_amount(tmp_path):
    """Reduce-only cappato dal netting (audit 2026-06-11): close richiesto 1.0 ma
    fillato 0.5 -> il ledger booka SOLO il fillato, REAL_CLOSE verified=False e
    evento REAL_CLOSE_PARTIAL col residuo orfano (niente piu' chiusure 'piene' finte)."""
    class PartialFake(FakeExec):
        def close_amount(self, instrument, side, amount, label=None):
            return Fill(instrument, "sell" if side == "buy" else "buy", 0.0, amount,
                        self.close_px, 0.0, 0.05, "oid-close", "filled", True,
                        filled_amount=amount * 0.5)

    fake = PartialFake(open_px=100.0, close_px=105.0)
    w = _sw(tmp_path, fake)
    _open_sim(w, 100.0)
    w._close_position(105.0, "time_limit")
    rows = [json.loads(l) for l in w.trades_path.read_text().strip().splitlines()]
    rc = [r for r in rows if r.get("event") == "REAL_CLOSE"][-1]
    assert rc["verified"] is False                         # chiusura NON completa
    partial = [r for r in rows if r.get("event") == "REAL_CLOSE_PARTIAL"]
    assert partial and partial[-1]["residuo_orfano"] > 0


def test_pairs_orphan_leg_close_not_booked(tmp_path):
    """Gamba A respinta in CHIUSURA (reduce-only vs netting, audit 2026-06-11): il suo
    'profitto' al prezzo sim NON va nel ledger reale, l'orfano e' registrato/persistito
    e il real-truth ricade sul sim DICHIARATO (applied=False)."""
    class LegFailClose(FakePairsExec):
        def close_pair(self, inst_a, inst_b, side_a, side_b, amount_a, amount_b, label=None):
            bad_a = Fill(inst_a, "sell", 0.0, amount_a, None, 0.0, 0.0, None, "error", False)
            ok_b = self._fill(inst_b, "buy", amount_b, self.px["close_b"])
            return PairFill(False, bad_a, ok_b)

    fake = LegFailClose(open_a=100.0, close_a=110.0)       # gamba A +10% ma MAI chiusa
    w = _pw(tmp_path, fake)
    w._open(1, 100.0, 50.0, -2.1)
    na = w.real_notional_a
    w._close(110.0, 50.0, 0.1, "mean_revert")
    assert w.orphan_legs and w.orphan_legs[0]["instrument"] == "ETH_USDC-PERPETUAL"
    rc = json.loads([l for l in w.trades_path.read_text().splitlines()
                     if '"REAL_CLOSE_PAIR"' in l][-1])
    assert rc["leg_a_ok"] is False and rc["leg_b_ok"] is True
    # il +10% della gamba A (mai eseguita) NON e' nel capitale reale
    assert w.real_capital < 100.0 + 0.10 * na - 0.05
    c = json.loads([l for l in w.trades_path.read_text().splitlines()
                    if '"event": "CLOSE"' in l][-1])
    assert c["pnl_source"] == "sim_fallback"
