def test_venue_string():
    from fxhnt.cli import _exec_venue_string
    assert _exec_venue_string("ibkr", True) == "ibkr-paper"
    assert _exec_venue_string("ibkr", False) == "ibkr-live"
    assert _exec_venue_string("bybit", True) == "bybit-paper"


def test_resolve_paper_envelope():
    import pytest
    import typer

    from fxhnt.cli import _resolve_paper_envelope
    assert _resolve_paper_envelope(1_000_000.0, 0.0) == 1_000_000.0   # flag wins
    assert _resolve_paper_envelope(0.0, 500_000.0) == 500_000.0       # config fallback
    assert _resolve_paper_envelope(0.0, 0.0) == 0.0                   # off
    # a negative flag is an operator typo, not "unset" — must be rejected, not silently fall back to the
    # config default (which would hide the typo behind a seemingly-valid run).
    with pytest.raises(typer.BadParameter):
        _resolve_paper_envelope(-5.0, 500_000.0)


def test_execute_bybit_command_registered():
    from fxhnt.cli import app
    names = {c.name for c in app.registered_commands}
    assert "execute-bybit" in names


def test_execute_bybit_paper_envelope_refused_is_distinct_from_traceback(monkeypatch, tmp_path):
    """A paper-envelope ValueError refusal from `run_bybit_testnet` must surface as a clear, distinct
    message + exit code 1 — NOT a raw traceback (the pre-fix behavior: `execute_bybit` had no try/except
    at all, so the ValueError propagated uncaught)."""
    from typer.testing import CliRunner

    import fxhnt.cli as climod

    dsn = f"sqlite:///{tmp_path / 'op.db'}"
    monkeypatch.setenv("FXHNT_OPERATIONAL_DSN", dsn)
    climod.get_settings.cache_clear()

    def _boom(settings, *, paper_envelope, execute, log):
        raise ValueError("paper-envelope refused: bybit account is not testnet/paper")

    monkeypatch.setattr("fxhnt.application.bybit_testnet_run.run_bybit_testnet", _boom)

    res = CliRunner().invoke(climod.app, ["execute-bybit", "--execute", "--paper-envelope", "5"])
    climod.get_settings.cache_clear()
    assert res.exit_code == 1, res.output
    assert "paper-envelope refused" in res.output
    assert "Traceback" not in res.output


def test_execute_multistrat_paper_envelope_refused_is_distinct_from_broker_error(monkeypatch, tmp_path, capsys):
    """A paper-envelope ValueError refusal from `plan_and_record` must be caught distinctly (BEFORE the
    generic `except Exception`) and printed as a clear safety-refusal message — NOT the misleading
    connectivity framing ('broker error: ... reachable? ... creds/gateway set?') the generic handler prints
    for every other exception. No live broker/network: YahooDataProvider and IbkrBroker are faked."""
    import numpy as np
    import pytest
    import typer

    import fxhnt.cli as climod
    from fxhnt.domain.models import PriceSeries
    from fxhnt.ports.broker import AccountState

    dsn = f"sqlite:///{tmp_path / 'op.db'}"
    monkeypatch.setenv("FXHNT_OPERATIONAL_DSN", dsn)
    climod.get_settings.cache_clear()

    # 120 synthetic daily bars per fund instrument (needs >= 66 for target_weights to be non-empty).
    rng = np.random.default_rng(7)
    n = 120
    dates = tuple(f"2026-{1 + i // 28:02d}-{1 + i % 28:02d}" for i in range(n))

    class _FakeData:
        def fetch(self, market):  # noqa: ANN001, ANN201
            close = 100.0 * np.cumprod(1 + rng.normal(0, 0.01, size=n))
            return PriceSeries(market=market, dates=dates, close=close)

    monkeypatch.setattr(climod, "YahooDataProvider", lambda: _FakeData())

    class _FakeBroker:
        name = "ibkr"
        supports_fractional = False

        def __enter__(self):  # noqa: ANN201
            return self

        def __exit__(self, *a):  # noqa: ANN002, ANN201
            return False

        def account_state(self):  # noqa: ANN201
            return AccountState(nlv=100_000.0, cash=100_000.0, is_paper=False)

    monkeypatch.setattr(climod, "IbkrBroker", lambda *a, **k: _FakeBroker())

    def _boom(**kwargs):  # noqa: ANN003, ANN201
        raise ValueError("paper-envelope refused: account is not paper (real-capital exposure guard)")

    monkeypatch.setattr("fxhnt.application.multistrat_exec_record.plan_and_record", _boom)

    with pytest.raises(typer.Exit) as ei:
        climod.execute_multistrat(do_execute=True, live=False, max_gross=2.0,
                                  paper_envelope=100.0, venue_kind="us")
    climod.get_settings.cache_clear()
    assert ei.value.exit_code == 1
    out = capsys.readouterr().out
    assert "paper-envelope refused" in out
    assert "broker error" not in out
    assert "reachable?" not in out
