"""PairsWorker esecuzione reale a 2 gambe (shadow): apertura/chiusura, leg-risk unwind,
ledger reale parallelo. Executor finto, nessuna rete."""
from types import SimpleNamespace

import numpy as np
import pandas as pd
import pytest

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


class FakePairsExec:
    """Simula PairsExecutionClient: fill deterministici, con possibilità di leg-fail."""
    def __init__(self, fail_leg=None):
        self.fail_leg = fail_leg          # None | 'a' | 'b' : quale gamba NON filla all'open
        self.opened = []
        self.closed = []
        self.unwound = []

    def _fill(self, inst, side, price, amt=1.0, ok=True):
        return Fill(inst, side, 0.0, amt if ok else 0.0, price if ok else None,
                    0.0, 0.05 if ok else 0.0, "oid" if ok else None,
                    "filled" if ok else "error", ok,
                    filled_amount=amt if ok else 0.0)

    def open_pair(self, inst_a, inst_b, direction, notional, label=None):
        side_a = "buy" if direction == 1 else "sell"
        side_b = "sell" if direction == 1 else "buy"
        ok_a = self.fail_leg != 'a'
        ok_b = self.fail_leg != 'b'
        fa = self._fill(inst_a, side_a, 100.0, ok=ok_a)
        fb = self._fill(inst_b, side_b, 50.0, ok=ok_b)
        self.opened.append((inst_a, inst_b, direction))
        if ok_a and ok_b:
            return PairFill(True, fa, fb)
        if ok_a and fa.amount > 0:
            self.unwound.append(inst_a)
        if ok_b and fb.amount > 0:
            self.unwound.append(inst_b)
        return PairFill(False, fa, fb, unwound=bool(self.unwound), notes="leg-fail")

    def close_pair(self, inst_a, inst_b, side_a, side_b, amt_a, amt_b, label=None):
        opp_a = "sell" if side_a == "buy" else "buy"
        opp_b = "sell" if side_b == "buy" else "buy"
        self.closed.append((inst_a, inst_b))
        # prezzi di uscita: A salito a 102, B fermo a 50 -> long-A/short-B guadagna
        return PairFill(True, self._fill(inst_a, opp_a, 102.0, amt_a),
                        self._fill(inst_b, opp_b, 50.0, amt_b))


INST = {"ETH": "ETH_USDC-PERPETUAL", "BTC": "BTC_USDC-PERPETUAL"}


def _worker(tmp_path, fake, monkeypatch, notified=None):
    if notified is not None:
        monkeypatch.setattr("src.live.pairs_worker.notify_event",
                            lambda ev, data=None: notified.append(ev))
    return PairsWorker("ETH", "BTC", "1h", capital=200.0, position_size=0.2, leverage=2.0,
                       data_dir=tmp_path, executor=fake, exec_instruments=INST)


def test_execution_enabled_wired(tmp_path):
    w = _worker(tmp_path, FakePairsExec(), pytest.MonkeyPatch())
    assert w.execution_enabled and w.inst_a == "ETH_USDC-PERPETUAL" and w.inst_b == "BTC_USDC-PERPETUAL"


def test_real_open_and_close_pair(tmp_path, monkeypatch):
    notified = []
    fake = FakePairsExec()
    w = _worker(tmp_path, fake, monkeypatch, notified)
    w._open(1, 100.0, 50.0, -2.5)                      # long ratio
    assert w.real_in_position and w.real_dir == 1
    assert w.real_side_a == "buy" and w.real_side_b == "sell"
    assert fake.opened and "REAL_EXEC_LIVE" in notified
    cap0 = w.real_capital
    w._close(102.0, 50.0, 0.3, "mean_revert")
    assert not w.real_in_position and fake.closed
    assert w.real_capital > cap0                        # A +2% / B 0 -> long-A/short-B in utile
    assert w.real_trades == 1


def test_leg_fail_unwinds_and_no_position(tmp_path, monkeypatch):
    notified = []
    fake = FakePairsExec(fail_leg='b')                  # gamba B non filla
    w = _worker(tmp_path, fake, monkeypatch, notified)
    w._open(1, 100.0, 50.0, -2.5)
    assert not w.real_in_position                       # niente posizione reale
    assert "ETH_USDC-PERPETUAL" in fake.unwound         # la gamba A fillata e' stata richiusa
    assert "REAL_OPEN_FAIL" in notified


def test_real_ledger_persists_and_resumes(tmp_path, monkeypatch):
    fake = FakePairsExec()
    w = _worker(tmp_path, fake, monkeypatch, [])
    w._open(-1, 100.0, 50.0, 2.5)                       # short ratio
    assert w.real_in_position and w.real_dir == -1
    w2 = PairsWorker("ETH", "BTC", "1h", capital=200.0, position_size=0.2, leverage=2.0,
                     data_dir=tmp_path, executor=fake, exec_instruments=INST)
    assert w2.real_in_position and w2.real_dir == -1
    assert w2.real_side_a == "sell" and w2.real_amount_a > 0
