from __future__ import annotations

import json

from src.tools import trader_desk_shadow_runner as runner


def _plan(coin="BTC", action="watch_shadow", direction="long", low="100", high="101", stop="98", tp1="104"):
    return {
        "coin": coin,
        "action": action,
        "direction": direction,
        "score": "78.00",
        "setup": "trend_retest_continuation",
        "entry_zone": {"low": low, "high": high, "style": "retest_limit_zone"},
        "stop_loss": stop,
        "take_profit": {"tp1": tp1, "tp2": "108", "management": "test"},
        "blockers": [],
        "requires_preflight": action == "tiny_live_candidate",
        "live_order_allowed": False,
        "mainnet_signed_action": False,
    }


def _payload(plans=None, timestamp="2026-06-30T00:00:00+00:00"):
    plans = plans if plans is not None else [_plan()]
    return {
        "status": "ok",
        "source": "test",
        "report": {
            "schema_version": "trader_desk.v1",
            "timestamp": timestamp,
            "mode": "read_only_professional_trader_desk",
            "market_regime": "risk_on",
            "summary": {"tiny_live_candidates": 0, "watch_shadow": 1, "blocked": 0, "next_step": "keep_shadow_watch_and_collect_evidence"},
            "plans": plans,
            "live_order_allowed": False,
            "mainnet_signed_action": False,
        },
        "live_order_allowed": False,
        "mainnet_signed_action": False,
    }


def _patch_runtime(monkeypatch, tmp_path):
    runtime_dir = tmp_path / "trader_desk_shadow"
    monkeypatch.setattr(runner, "RUNTIME_DIR", runtime_dir)
    monkeypatch.setattr(runner, "STATE_PATH", runtime_dir / "state.json")
    monkeypatch.setattr(runner, "DECISIONS", runtime_dir / "decision_journal.jsonl")
    monkeypatch.setattr(runner, "OUTCOMES", runtime_dir / "outcome_journal.jsonl")
    monkeypatch.setattr(runner, "HEALTH", runtime_dir / "runtime_health.jsonl")
    monkeypatch.setattr(runner, "LATEST_REPORT", runtime_dir / "trader_desk_latest.json")
    monkeypatch.setattr(runner, "PID_PATH", runtime_dir / "runner.pid")
    return runtime_dir


def test_shadow_runner_journals_decisions_opens_shadow_and_exports_finance(monkeypatch, tmp_path):
    runtime_dir = _patch_runtime(monkeypatch, tmp_path)
    finance_target = tmp_path / "finance" / "trader_desk_latest.json"
    monkeypatch.setattr(runner, "build_report", lambda **_kwargs: _payload())

    result = runner.run_once(coins=("BTC",), finance_output=finance_target)

    assert result["status"] == "ok"
    assert result["decisions"] == 1
    assert result["opened"] == 1
    assert result["closed"] == 0
    assert result["paper_only"] is True
    assert result["live_order_allowed"] is False
    assert result["mainnet_signed_action"] is False
    assert finance_target.exists()
    finance_payload = json.loads(finance_target.read_text())
    assert finance_payload["safety_boundary"]["execution_allowed"] is False
    assert finance_payload["trade_intents"][0]["dashboard_executes_orders"] is False
    decisions = [json.loads(line) for line in (runtime_dir / "decision_journal.jsonl").read_text().splitlines()]
    outcomes = [json.loads(line) for line in (runtime_dir / "outcome_journal.jsonl").read_text().splitlines()]
    assert decisions[0]["event"] == "desk_decision"
    assert decisions[0]["live_order_allowed"] is False
    assert outcomes[0]["event"] == "shadow_open"
    state = json.loads((runtime_dir / "state.json").read_text())
    assert "BTC" in state["positions"]
    assert state["paper_only"] is True


def test_shadow_runner_closes_shadow_position_on_stop(monkeypatch, tmp_path):
    runtime_dir = _patch_runtime(monkeypatch, tmp_path)
    monkeypatch.setattr(runner, "build_report", lambda **_kwargs: _payload())
    finance_target = tmp_path / "finance" / "trader_desk_latest.json"
    first = runner.run_once(coins=("BTC",), finance_output=finance_target)
    assert first["opened"] == 1

    monkeypatch.setattr(
        runner,
        "build_report",
        lambda **_kwargs: _payload(plans=[_plan(low="97", high="97", stop="98", tp1="104")], timestamp="2026-06-30T00:05:00+00:00"),
    )
    second = runner.run_once(coins=("BTC",), finance_output=finance_target)

    assert second["closed"] == 1
    assert second["shadow_performance"]["closed_hypotheses"] == 1
    assert second["shadow_performance"]["losses"] == 1
    outcomes = [json.loads(line) for line in (runtime_dir / "outcome_journal.jsonl").read_text().splitlines()]
    close = outcomes[-1]
    assert close["event"] == "shadow_close"
    assert close["reason"] == "shadow_stop_hit"
    assert close["setup"] == "trend_retest_continuation"
    assert close["score"] == "78.00"
    assert close["source_action"] == "watch_shadow"
    assert close["mainnet_signed_action"] is False
    state = json.loads((runtime_dir / "state.json").read_text())
    assert "BTC" not in state["positions"]


def test_shadow_runner_main_records_degraded_without_live_side_effects(monkeypatch, tmp_path, capsys):
    runtime_dir = _patch_runtime(monkeypatch, tmp_path)
    monkeypatch.setattr(runner.time, "sleep", lambda _seconds: None)

    def broken(**_kwargs):
        raise ConnectionError("No route to host")

    monkeypatch.setattr(runner, "build_report", broken)

    assert runner.main(["--coins", "BTC", "--iterations", "1", "--json", "--no-export-finance"]) == 0
    payload = json.loads(capsys.readouterr().out)
    assert payload["status"] == "degraded"
    assert payload["results"][0]["entries_blocked"] is True
    assert payload["results"][0]["paper_only"] is True
    assert payload["results"][0]["mainnet_signed_action"] is False
    health = [json.loads(line) for line in (runtime_dir / "runtime_health.jsonl").read_text().splitlines()]
    assert health[-1]["event"] == "shadow_tick_degraded"
