from decimal import Decimal
import json

from src.tools import market_regime_shadow_trader as trader
from src.tools.market_regime_shadow_trader import (
    D,
    expected_round_trip_cost_pct,
    extract_funding_rates,
    funding_carry_24h_pct,
    score_coin,
)


def base_short_features(**overrides):
    features = {
        "coin": "LINK",
        "close": Decimal("95"),
        "ema20_1h": Decimal("100"),
        "ema50_1h": Decimal("105"),
        "ema20_4h": Decimal("110"),
        "ema50_4h": Decimal("115"),
        "rsi_1h": Decimal("42"),
        "atr_pct": Decimal("1.2"),
        "ret_4h_pct": Decimal("-1.20"),
        "ret_24h_pct": Decimal("-2.50"),
        "vol_ratio": Decimal("1.1"),
    }
    features.update(overrides)
    return features


def test_expected_round_trip_cost_includes_fee_spread_and_safety_margin():
    assert expected_round_trip_cost_pct(
        spread_pct=Decimal("0.03"),
        taker_fee_rate=Decimal("0.00035"),
        safety_margin_pct=Decimal("0.05"),
    ) == Decimal("0.15")


def test_extract_funding_rates_from_meta_and_asset_contexts_payload():
    payload = [
        {"universe": [{"name": "ETH"}, {"name": "TON"}]},
        [{"funding": "0.000003"}, {"funding": "-0.000041"}],
    ]

    assert extract_funding_rates(payload) == {
        "ETH": Decimal("0.000003"),
        "TON": Decimal("-0.000041"),
    }


def test_funding_carry_for_short_is_positive_when_funding_positive():
    assert funding_carry_24h_pct("short", Decimal("0.0000125")) == Decimal("0.03")


def test_funding_carry_for_short_is_negative_when_funding_negative():
    assert funding_carry_24h_pct("short", Decimal("-0.0000411443")) == Decimal("-0.0987463200")


def test_bearish_short_is_blocked_when_edge_does_not_clear_costs():
    side, score, reasons = score_coin(
        base_short_features(ret_4h_pct=Decimal("-0.18")),
        "bearish",
        spread_pct=Decimal("0.04"),
        funding_rate=Decimal("0.0000125"),
        taker_fee_rate=Decimal("0.00035"),
        min_edge_multiple=Decimal("2"),
    )

    assert side == "short"
    assert "edge_too_small_after_costs" in reasons
    assert score < Decimal("1")


def test_bearish_short_is_blocked_when_funding_is_against_short():
    side, score, reasons = score_coin(
        base_short_features(),
        "bearish",
        spread_pct=Decimal("0.03"),
        funding_rate=Decimal("-0.0000411443"),
        taker_fee_rate=Decimal("0.00035"),
    )

    assert side == "short"
    assert "funding_against_short" in reasons
    assert score < Decimal("2")


def test_bearish_short_is_blocked_when_move_is_overextended_and_oversold():
    side, score, reasons = score_coin(
        base_short_features(ret_24h_pct=Decimal("-8.2"), rsi_1h=Decimal("29")),
        "bearish",
        spread_pct=Decimal("0.03"),
        funding_rate=Decimal("0.0000125"),
        taker_fee_rate=Decimal("0.00035"),
    )

    assert side == "short"
    assert "short_chase_risk_overextended" in reasons
    assert score < Decimal("2")


def test_quality_short_passes_when_edge_funding_and_reversal_risk_are_clean():
    side, score, reasons = score_coin(
        base_short_features(ret_4h_pct=Decimal("-1.35"), ret_24h_pct=Decimal("-3.5"), rsi_1h=Decimal("41")),
        "bearish",
        spread_pct=Decimal("0.025"),
        funding_rate=Decimal("0.0000125"),
        taker_fee_rate=Decimal("0.00035"),
    )

    assert side == "short"
    assert reasons == []
    assert score > Decimal("2")


def test_main_records_api_degraded_and_keeps_paper_closed(monkeypatch, tmp_path, capsys):
    runtime_dir = tmp_path / "shadow"
    monkeypatch.setattr(trader, "RUNTIME_DIR", runtime_dir)
    monkeypatch.setattr(trader, "STATE_PATH", runtime_dir / "state.json")
    monkeypatch.setattr(trader, "SIGNALS", runtime_dir / "signal_journal.jsonl")
    monkeypatch.setattr(trader, "TRADES", runtime_dir / "trade_journal.jsonl")
    monkeypatch.setattr(trader, "HEALTH", runtime_dir / "runtime_health.jsonl")
    monkeypatch.setattr(trader, "PID_PATH", runtime_dir / "bot.pid")
    monkeypatch.setattr(trader.time, "sleep", lambda _seconds: None)

    def degraded(_coins):
        raise ConnectionError("No route to host")

    monkeypatch.setattr(trader, "run_once", degraded)

    assert trader.main(["--coins", "BTC", "--iterations", "1", "--json"]) == 0
    payload = json.loads(capsys.readouterr().out)
    assert payload["status"] == "degraded"
    assert payload["results"][0]["event"] == "api_degraded"
    assert payload["results"][0]["entries_blocked"] is True
    assert payload["results"][0]["paper_trading"] is True
    assert payload["results"][0]["mainnet_signed_action"] is False
    assert not (runtime_dir / "trade_journal.jsonl").exists()
    health = [json.loads(line) for line in (runtime_dir / "runtime_health.jsonl").read_text().splitlines()]
    assert health[-1]["event"] == "api_degraded"
