from __future__ import annotations

from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation
import hashlib
import json
import os
from pathlib import Path
from typing import Any

from jarvis_finance.api.schemas.crypto_trader import (
    CryptoTraderJournalStatus,
    CryptoTraderScorecard,
    CryptoTraderSignal,
    CryptoTraderSnapshot,
    CryptoTraderStatus,
    CryptoTraderStrategyStatus,
    HyperliquidClosedTrade,
    HyperliquidPerformancePoint,
    HyperliquidPortfolioSnapshot,
    HyperliquidPosition,
    HyperliquidRiskCheck,
    HyperliquidStrategyPerformance,
    HyperliquidWarningExplanation,
    TradeApprovalRequest,
    TradeApprovalResponse,
    TradeIntent,
)

DEFAULT_CRYPTO_TRADER_ROOT = Path("/home/agent/projects/CryptoTradingBot/Crypto_Agent")
RUNTIME_ENV = "JARVIS_CRYPTO_TRADER_RUNTIME_ROOT"
PROJECT_ENV = "JARVIS_CRYPTO_TRADER_PROJECT_ROOT"
FINANCE_RUNTIME_ENV = "JARVIS_FINANCE_RUNTIME_DIR"
VALID_APPROVAL_DECISIONS = {"approve", "reject", "paper_only"}


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _runtime_root() -> Path:
    explicit = os.getenv(RUNTIME_ENV)
    if explicit:
        return Path(explicit).expanduser()
    project = Path(os.getenv(PROJECT_ENV, str(DEFAULT_CRYPTO_TRADER_ROOT))).expanduser()
    return project / "runtime"


def _finance_runtime_root() -> Path:
    return Path(os.getenv(FINANCE_RUNTIME_ENV, "/home/agent/jarvis_runtime/finance-system")).expanduser()


def _approval_log_path() -> Path:
    return _finance_runtime_root() / "crypto_trader" / "trade_approvals.jsonl"


def _approval_id(intent_id: str, decision: str, created_at: str) -> str:
    digest = hashlib.sha256(f"{intent_id}|{decision}|{created_at}".encode("utf-8")).hexdigest()[:16]
    return f"approval-{digest}"


def _stable_intent_id(row: dict[str, Any], strategy_id: str, coin: str) -> str:
    basis = "|".join(
        [
            str(row.get("timestamp") or ""),
            strategy_id,
            coin,
            str(row.get("final_decision") or ""),
            str(row.get("entry") or row.get("price") or ""),
            str(row.get("signal_score") or ""),
        ]
    )
    return "intent-" + hashlib.sha256(basis.encode("utf-8")).hexdigest()[:20]


def _load_approval_decisions(path: Path | None = None) -> dict[str, dict[str, Any]]:
    log_path = path or _approval_log_path()
    decisions: dict[str, dict[str, Any]] = {}
    for row in _tail_jsonl(log_path, limit=500):
        intent_id = row.get("intent_id")
        if isinstance(intent_id, str) and intent_id:
            decisions[intent_id] = row
    return decisions


def _read_json(path: Path) -> dict[str, Any]:
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except (FileNotFoundError, json.JSONDecodeError, OSError):
        return {}
    return payload if isinstance(payload, dict) else {}


def _tail_jsonl(path: Path, limit: int = 80) -> list[dict[str, Any]]:
    if not path.exists() or limit <= 0:
        return []
    try:
        lines = path.read_text(encoding="utf-8", errors="replace").splitlines()[-limit:]
    except OSError:
        return []
    rows: list[dict[str, Any]] = []
    for line in lines:
        if not line.strip():
            continue
        try:
            item = json.loads(line)
        except json.JSONDecodeError:
            continue
        if isinstance(item, dict):
            rows.append(item)
    return rows


def _age_seconds(path: Path) -> int | None:
    try:
        return max(0, int(datetime.now().timestamp() - path.stat().st_mtime))
    except OSError:
        return None


def _journal_status(root: Path, payload: dict[str, Any]) -> dict[str, CryptoTraderJournalStatus]:
    out: dict[str, CryptoTraderJournalStatus] = {}
    raw_obj = payload.get("journals")
    raw: dict[str, Any] = raw_obj if isinstance(raw_obj, dict) else {}
    for name, value in raw.items():
        if isinstance(value, dict):
            out[str(name)] = CryptoTraderJournalStatus(
                exists=bool(value.get("exists")),
                fresh=bool(value.get("fresh")),
                age_seconds=value.get("age_seconds") if isinstance(value.get("age_seconds"), int) else None,
                size_bytes=int(value.get("size_bytes") or 0),
            )
    for key in ("trade_journal", "signal_journal"):
        rel = payload.get(key)
        if not isinstance(rel, str) or not rel:
            continue
        name = Path(rel).name
        if name in out:
            continue
        path = root.parent / rel if not Path(rel).is_absolute() else Path(rel)
        exists = path.exists()
        age = _age_seconds(path)
        out[name] = CryptoTraderJournalStatus(exists=exists, fresh=bool(exists and age is not None and age <= 900), age_seconds=age, size_bytes=path.stat().st_size if exists else 0)
    return out


def _strategy_status(root: Path, strategy_id: str, payload: dict[str, Any]) -> CryptoTraderStrategyStatus:
    return CryptoTraderStrategyStatus(
        strategy_id=strategy_id,
        running=bool(payload.get("running")),
        paper_only=bool(payload.get("paper_only", True)),
        env_ok=bool(payload.get("env_ok")),
        cmd_ok=bool(payload.get("cmd_ok")),
        pid=payload.get("pid") if isinstance(payload.get("pid"), int) else None,
        runtime_dir=payload.get("runtime_dir") if isinstance(payload.get("runtime_dir"), str) else None,
        trade_journal=payload.get("trade_journal") if isinstance(payload.get("trade_journal"), str) else None,
        signal_journal=payload.get("signal_journal") if isinstance(payload.get("signal_journal"), str) else None,
        journals=_journal_status(root, payload),
    )


def _load_processes(root: Path) -> list[CryptoTraderStrategyStatus]:
    supervisor = _read_json(root / "reports" / "v76_paper_supervisor_latest.json")
    sources: dict[str, Any] = {}
    for section in ("after", "before"):
        value = supervisor.get(section)
        if isinstance(value, dict):
            sources.update({str(k): v for k, v in value.items() if isinstance(v, dict)})
    if not sources:
        experiments = root / "experiments"
        for state_path in sorted(experiments.glob("*/state.json")):
            strategy_id = state_path.parent.name
            sources[strategy_id] = {"runtime_dir": str(state_path.parent), "paper_only": True, "running": False, "env_ok": False, "cmd_ok": False}
    return [_strategy_status(root, strategy_id, payload) for strategy_id, payload in sorted(sources.items())]


def _to_decimal(value: Any) -> Decimal:
    try:
        return Decimal(str(value))
    except (InvalidOperation, TypeError, ValueError):
        return Decimal("0")


def _fmt_money(value: Decimal) -> str:
    return str(value.quantize(Decimal("0.01")))


def _optional_money(value: Any) -> str | None:
    if value is None:
        return None
    return _fmt_money(_to_decimal(value))


def _positions_from_state(root: Path) -> list[HyperliquidPosition]:
    positions: list[HyperliquidPosition] = []
    for state_path in sorted((root / "experiments").glob("*/state.json")):
        state = _read_json(state_path)
        mids = state.get("prev_mids") if isinstance(state.get("prev_mids"), dict) else {}
        raw_positions = state.get("open_positions")
        if not isinstance(raw_positions, dict):
            continue
        for coin, entry in sorted(raw_positions.items()):
            if not isinstance(entry, dict):
                continue
            coin_symbol = str(coin).upper()
            price = _to_decimal(entry.get("entry") or entry.get("entry_price"))
            mark = _to_decimal(entry.get("mark_price") or entry.get("mark") or mids.get(coin_symbol) or mids.get(str(coin)))
            size = _to_decimal(entry.get("size"))
            side = str(entry.get("side") or "long").lower()
            notional_basis = mark if mark else price
            unrealized = (mark - price) * size if side != "short" else (price - mark) * size
            ticks_raw = entry.get("ticks_held")
            ticks_held = ticks_raw if isinstance(ticks_raw, int) else None
            positions.append(
                HyperliquidPosition(
                    coin=coin_symbol,
                    strategy_id=str(entry.get("strategy_id") or state_path.parent.name),
                    mode="paper" if entry.get("paper_trading", True) is not False else "unknown",
                    side=side,
                    entry_price=str(entry.get("entry") or entry.get("entry_price")) if entry.get("entry") or entry.get("entry_price") else None,
                    mark_price=str(mark) if mark else None,
                    size=str(entry.get("size")) if entry.get("size") is not None else None,
                    notional_usd=_fmt_money(abs(notional_basis * size)) if notional_basis and size else None,
                    unrealized_pnl_usd=_fmt_money(unrealized) if mark and price and size else None,
                    unrealized_pnl_pct=_fmt_money((unrealized / abs(price * size)) * Decimal("100")) if mark and price and size else None,
                    stop_loss=str(entry.get("stop_loss")) if entry.get("stop_loss") is not None else None,
                    take_profit=str(entry.get("take_profit")) if entry.get("take_profit") is not None else None,
                    mfe_pct=str(entry.get("mfe_pct")) if entry.get("mfe_pct") is not None else None,
                    mae_pct=str(entry.get("mae_pct")) if entry.get("mae_pct") is not None else None,
                    ticks_held=ticks_held,
                    opened_at=str(entry.get("opened_at")) if entry.get("opened_at") is not None else None,
                    status="open_paper",
                )
            )
    return positions


def _closed_trade_from_row(row: dict[str, Any], strategy_id: str, idx: int) -> HyperliquidClosedTrade:
    entry_fee = _to_decimal(row.get("entry_fee_usd"))
    exit_fee = _to_decimal(row.get("exit_fee_usd"))
    slippage = _to_decimal(row.get("slippage_cost_usd"))
    funding = _to_decimal(row.get("funding_cost_usd"))
    fees = entry_fee + exit_fee + slippage + funding
    candles_raw = row.get("candles_held")
    return HyperliquidClosedTrade(
        trade_id=f"trade-{strategy_id}-{row.get('timestamp', '')}-{row.get('coin', '')}-{idx}",
        timestamp=str(row.get("timestamp")) if row.get("timestamp") else None,
        coin=str(row.get("coin") or "UNKNOWN").upper(),
        strategy_id=str(row.get("strategy_id") or strategy_id),
        side=str(row.get("side") or "long"),
        size=str(row.get("size") or row.get("filled_size")) if row.get("size") or row.get("filled_size") else None,
        entry_price=str(row.get("entry_price")) if row.get("entry_price") is not None else None,
        exit_price=str(row.get("exit_price")) if row.get("exit_price") is not None else None,
        net_pnl_usd=_optional_money(row.get("net_pnl_usd")),
        gross_pnl_usd=_optional_money(row.get("gross_pnl_usd")),
        fees_usd=_fmt_money(fees) if fees else None,
        funding_cost_usd=_optional_money(row.get("funding_cost_usd")),
        exit_reason=str(row.get("exit_reason")) if row.get("exit_reason") is not None else None,
        candles_held=candles_raw if isinstance(candles_raw, int) else None,
        mode="paper" if row.get("paper_trading", True) is not False else "unknown",
    )


def _load_closed_trades(root: Path, limit: int = 200, stats_limit: int = 1200) -> tuple[list[HyperliquidClosedTrade], Decimal, int, str | None, list[HyperliquidPerformancePoint], list[HyperliquidStrategyPerformance], Decimal, int, str | None]:
    rows: list[dict[str, Any]] = []
    by_strategy: dict[str, list[dict[str, Any]]] = {}
    for path in sorted((root / "experiments").glob("*/trade_journal.jsonl")):
        strategy_rows = [{**row, "_strategy_id": path.parent.name} for row in _tail_jsonl(path, limit=stats_limit) if row.get("event") == "exit"]
        if strategy_rows:
            is_stale_or_synthetic = ".invalid_synthetic" in path.parent.name or ((_age_seconds(path) or 0) > 604800)
            if not is_stale_or_synthetic:
                by_strategy[path.parent.name] = strategy_rows
            rows.extend(strategy_rows)
    rows.sort(key=lambda r: str(r.get("timestamp") or ""), reverse=True)

    strict_rows = by_strategy.get("candidate_v76_strict_live_candidate", [])[-100:]
    headline_rows = strict_rows or rows[:100]
    realized = sum((_to_decimal(row.get("net_pnl_usd")) for row in headline_rows), Decimal("0"))
    closed_count = len(headline_rows)
    wins = sum(1 for row in headline_rows if _to_decimal(row.get("net_pnl_usd")) > 0)
    winrate = _fmt_money((Decimal(wins) / Decimal(closed_count)) * Decimal("100")) if closed_count else None

    legacy_realized = sum((_to_decimal(row.get("net_pnl_usd")) for row in rows), Decimal("0"))
    legacy_count = len(rows)
    legacy_wins = sum(1 for row in rows if _to_decimal(row.get("net_pnl_usd")) > 0)
    legacy_winrate = _fmt_money((Decimal(legacy_wins) / Decimal(legacy_count)) * Decimal("100")) if legacy_count else None

    performance: list[HyperliquidPerformancePoint] = []
    cumulative = Decimal("0")
    for row in sorted(headline_rows, key=lambda r: str(r.get("timestamp") or "")):
        cumulative += _to_decimal(row.get("net_pnl_usd"))
        ts = str(row.get("timestamp") or "")
        if ts:
            performance.append(HyperliquidPerformancePoint(timestamp=ts, paper_pnl_usd=_fmt_money(cumulative)))

    strategy_cards: list[HyperliquidStrategyPerformance] = []
    for strategy_id, strategy_rows in sorted(by_strategy.items()):
        window = strategy_rows[-100:]
        pnl = sum((_to_decimal(row.get("net_pnl_usd")) for row in window), Decimal("0"))
        n = len(window)
        strategy_wins = sum(1 for row in window if _to_decimal(row.get("net_pnl_usd")) > 0)
        strategy_winrate = _fmt_money((Decimal(strategy_wins) / Decimal(n)) * Decimal("100")) if n else None
        avg = _fmt_money(pnl / Decimal(n)) if n else None
        status = "profitable" if pnl > 0 else "loss_making" if n else "no_closed_trades"
        note = "last_100_exits" if n == 100 else "small_sample"
        strategy_cards.append(HyperliquidStrategyPerformance(strategy_id=strategy_id, label=strategy_id.replace("candidate_", "").replace("_", " "), closed_trades=n, winrate_pct=strategy_winrate, net_pnl_usd=_fmt_money(pnl), avg_pnl_usd=avg, status=status, note=note))
    strategy_cards.sort(key=lambda card: _to_decimal(card.net_pnl_usd), reverse=True)

    recent = [_closed_trade_from_row(row, str(row.get("_strategy_id") or row.get("strategy_id") or "unknown"), idx) for idx, row in enumerate(rows[:limit], start=1)]
    return recent, realized, closed_count, winrate, performance, strategy_cards, legacy_realized, legacy_count, legacy_winrate


def _load_reconcile(root: Path) -> dict[str, Any]:
    payload = _read_json(root / "reports" / "hl_reconcile_watchdog_latest.json")
    reconcile = payload.get("reconcile") if isinstance(payload.get("reconcile"), dict) else {}
    watchdog = payload.get("watchdog") if isinstance(payload.get("watchdog"), dict) else {}
    return {"reconcile": reconcile, "watchdog": watchdog}


def _risk_checks(root: Path, processes: list[CryptoTraderStrategyStatus], reconcile_payload: dict[str, Any], kill_switch_status: str) -> list[HyperliquidRiskCheck]:
    reconcile = reconcile_payload.get("reconcile") if isinstance(reconcile_payload.get("reconcile"), dict) else {}
    watchdog = reconcile_payload.get("watchdog") if isinstance(reconcile_payload.get("watchdog"), dict) else {}
    checks: list[HyperliquidRiskCheck] = []
    checks.append(HyperliquidRiskCheck(key="kill_switch", label="Kill Switch", status="critical" if kill_switch_status == "active" else "ok", detail=kill_switch_status))
    rec_status = str(reconcile.get("status") or watchdog.get("status") or "unknown")
    checks.append(HyperliquidRiskCheck(key="reconcile", label="Reconcile", status="ok" if rec_status == "ok" else "warning", detail=rec_status))
    stops_missing = int(_to_decimal(reconcile.get("stops_missing_count") or watchdog.get("stops_missing_count") or 0))
    checks.append(HyperliquidRiskCheck(key="stops", label="Stop Orders", status="ok" if stops_missing == 0 else "critical", detail=f"missing={stops_missing}"))
    dq_warning = any("data_quality" in warning.lower() for warning in reconcile.get("warnings", []) if isinstance(warning, str))
    checks.append(HyperliquidRiskCheck(key="data_quality", label="API/Data Quality", status="warning" if dq_warning else "ok", detail="warning" if dq_warning else "ok"))
    all_paper = all(p.paper_only for p in processes) if processes else True
    checks.append(HyperliquidRiskCheck(key="execution_mode", label="Execution Mode", status="ok" if all_paper else "critical", detail="paper_only" if all_paper else "non_paper_process_detected"))
    return checks


def _signal_label(row: dict[str, Any]) -> str:
    final = str(row.get("final_decision") or "")
    if row.get("would_enter") is True or "paper_opened" in final or "entered" in final:
        return "BUY"
    if final.startswith("blocked") or row.get("block_reason"):
        return "HOLD"
    return "WATCH"


def _signal_reason(row: dict[str, Any]) -> str:
    reasons = row.get("block_reason")
    if isinstance(reasons, list) and reasons:
        return ", ".join(str(r) for r in reasons[:4])
    final = row.get("final_decision")
    if final:
        return str(final)
    setup = row.get("setup_type")
    return str(setup or "No actionable setup")


def _load_signals(root: Path, limit: int = 40) -> list[CryptoTraderSignal]:
    rows: list[dict[str, Any]] = []
    for path in sorted((root / "experiments").glob("*/signal_journal.jsonl")):
        rows.extend({**row, "_strategy_id": path.parent.name} for row in _tail_jsonl(path, limit=20))
    rows.sort(key=lambda r: str(r.get("timestamp") or ""), reverse=True)
    out: list[CryptoTraderSignal] = []
    for idx, row in enumerate(rows[:limit], start=1):
        live_flag = bool(row.get("live_order_allowed") or row.get("mainnet_signed_action"))
        out.append(
            CryptoTraderSignal(
                signal_id=f"signal-{idx}-{row.get('timestamp', '')}-{row.get('coin', '')}",
                timestamp=str(row.get("timestamp")) if row.get("timestamp") else None,
                coin=str(row.get("coin") or "UNKNOWN").upper(),
                strategy_id=str(row.get("strategy_id") or row.get("_strategy_id") or "unknown"),
                signal=_signal_label(row),
                confidence=str(row.get("signal_score") or row.get("expected_move_vs_cost")) if row.get("signal_score") or row.get("expected_move_vs_cost") else None,
                final_decision=str(row.get("final_decision")) if row.get("final_decision") else None,
                reason_summary=_signal_reason(row),
                horizon="swing" if "swing" in str(row.get("strategy_id") or row.get("_strategy_id")) else "short_term",
                mode="paper_only",
                data_quality="ok" if row.get("data_quality_allowed") is True else "warning",
                approval_status="not_requested",
                execution_allowed=False if not live_flag else False,
            )
        )
    return out


def _trade_intent_from_signal(row: dict[str, Any], approval_decisions: dict[str, dict[str, Any]]) -> TradeIntent:
    strategy_id = str(row.get("strategy_id") or row.get("_strategy_id") or "unknown")
    coin = str(row.get("coin") or "UNKNOWN").upper()
    intent_id = _stable_intent_id(row, strategy_id, coin)
    approval = approval_decisions.get(intent_id, {})
    decision = str(approval.get("decision") or "")
    approval_status = {"approve": "approved_pending_trader_gate", "reject": "rejected", "paper_only": "paper_only_approved"}.get(decision, "awaiting_approval")
    risk_reasons = []
    risk_gate = row.get("risk_gate_result")
    if isinstance(risk_gate, dict):
        raw_reasons = risk_gate.get("reasons")
        if isinstance(raw_reasons, list):
            risk_reasons = [str(reason) for reason in raw_reasons[:4]]
    warnings = ["dashboard_approval_does_not_execute", "crypto_trader_must_recheck_gates"]
    if row.get("data_quality_allowed") is not True:
        warnings.append("data_quality_not_green")
    warnings.extend(risk_reasons)
    return TradeIntent(
        intent_id=intent_id,
        created_at=str(row.get("timestamp")) if row.get("timestamp") else None,
        strategy_id=strategy_id,
        coin=coin,
        side="long",
        signal=_signal_label(row),
        confidence=str(row.get("signal_score") or row.get("expected_move_vs_cost")) if row.get("signal_score") or row.get("expected_move_vs_cost") else None,
        entry_context=_signal_reason(row),
        expected_move_vs_cost=str(row.get("expected_move_vs_cost")) if row.get("expected_move_vs_cost") else None,
        status="proposed" if not decision else "decision_recorded",
        approval_status=approval_status,
        execution_allowed=False,
        warnings=warnings,
    )


def _load_trade_intents(root: Path, limit: int = 20) -> list[TradeIntent]:
    rows: list[dict[str, Any]] = []
    for path in sorted((root / "experiments").glob("*/signal_journal.jsonl")):
        rows.extend({**row, "_strategy_id": path.parent.name} for row in _tail_jsonl(path, limit=60))
    candidate_rows = [
        row
        for row in rows
        if row.get("would_enter") is True or "paper_opened" in str(row.get("final_decision") or "") or "paper_entered" in str(row.get("final_decision") or "")
    ]
    candidate_rows.sort(key=lambda r: str(r.get("timestamp") or ""), reverse=True)
    approvals = _load_approval_decisions()
    seen: set[str] = set()
    intents: list[TradeIntent] = []
    for row in candidate_rows:
        intent = _trade_intent_from_signal(row, approvals)
        if intent.intent_id in seen:
            continue
        seen.add(intent.intent_id)
        intents.append(intent)
        if len(intents) >= limit:
            break
    return intents


def _parse_daily_scorecards(root: Path) -> tuple[list[CryptoTraderScorecard], list[str], list[str]]:
    path = root / "reports" / "hyperliquid_daily_report_latest.md"
    if not path.exists():
        return [], [], ["daily_report_missing"]
    try:
        lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
    except OSError:
        return [], [], ["daily_report_unreadable"]
    scorecards: list[CryptoTraderScorecard] = []
    warnings: list[str] = []
    excerpt = [line for line in lines[:35] if line.strip()]
    for line in lines:
        if "Lifecycle Scorecard" in line:
            blockers = []
            if "blocker=" in line:
                blockers = [b.strip() for b in line.split("blocker=", 1)[1].split(",") if b.strip() and not b.startswith("live=")]
            scorecards.append(CryptoTraderScorecard(title="v76 Lifecycle Scorecard", status="blocked" if blockers else "candidate", mode="paper_only", blockers=blockers[:6], live_allowed=False, net_pnl_usd=_extract_after(line, "net_pnl=", " USDC"), winrate_pct=_extract_after(line, "winrate=", "%"), profit_factor=_extract_after(line, "pf=", ",")))
        if "Promotion Gates" in line and "status=" in line:
            blockers = []
            if "blocker=" in line:
                blockers = [b.strip() for b in line.split("blocker=", 1)[1].split(",") if b.strip() and not b.startswith("live=")]
            scorecards.append(CryptoTraderScorecard(title="Promotion Gates", status="paper_only", mode="paper_only", blockers=blockers[:6], live_allowed=False, net_pnl_usd=_extract_after(line, "net_pnl=", " USDC"), winrate_pct=_extract_after(line, "winrate=", "%")))
        if "API/Data Quality:" in line and "warning" in line.lower():
            warnings.append("api_data_quality_warning")
        if "Reconcile" in line and "nicht sauber" in line:
            warnings.append("reconcile_not_clean")
    return scorecards, excerpt, warnings


def _extract_after(text: str, prefix: str, suffix: str) -> str | None:
    if prefix not in text:
        return None
    tail = text.split(prefix, 1)[1]
    if suffix in tail:
        return tail.split(suffix, 1)[0].strip()
    return tail.split(",", 1)[0].strip()


def _warning_explanations(warnings: list[str], reconcile_payload: dict[str, Any]) -> list[HyperliquidWarningExplanation]:
    reconcile = reconcile_payload.get("reconcile") if isinstance(reconcile_payload.get("reconcile"), dict) else {}
    watchdog = reconcile_payload.get("watchdog") if isinstance(reconcile_payload.get("watchdog"), dict) else {}
    alerts = [str(item) for item in watchdog.get("alerts", []) if isinstance(item, str)] + [str(item) for item in reconcile.get("alerts", []) if isinstance(item, str)]
    out: list[HyperliquidWarningExplanation] = []
    if "api_data_quality_warning" in warnings:
        out.append(HyperliquidWarningExplanation(key="api_data_quality_warning", severity="warning", title="API/Data Quality nicht vollständig grün", explanation="Der Tagesbericht markiert die Marktdaten-/API-Qualität als warning. Das blockiert aktuell keine neuen Entries, macht Signale aber weniger belastbar.", action="Als Watch behandeln; keine Live-Erhöhung, bis der nächste Tagesbericht wieder grün ist."))
    if "reconcile_not_clean" in warnings or alerts:
        detail = ", ".join(sorted(set(alerts))) or "Reconcile im Tagesbericht nicht sauber."
        out.append(HyperliquidWarningExplanation(key="reconcile_not_clean", severity="warning", title="Reconcile/Journal nicht komplett sauber", explanation=f"Es gibt Abgleich-Hinweise zwischen lokalem Journal und Exchange-State: {detail}. Der aktuelle Exchange-Reconcile kann trotzdem status=ok melden.", action="Kein Live-Auto. Paper weiter beobachten; lokale Phantom-/Altpositionen im Trader bereinigen."))
    return out


def _kill_switch_status(root: Path) -> str:
    candidates = [root / "KILL_SWITCH", root / "kill_switch", root / "config" / "KILL_SWITCH"]
    return "active" if any(p.exists() for p in candidates) else "inactive"


def build_crypto_trader_snapshot(runtime_root: Path | None = None) -> CryptoTraderSnapshot:
    root = runtime_root or _runtime_root()
    warnings: list[str] = []
    if not root.exists():
        warnings.append("runtime_root_missing")
    processes = _load_processes(root) if root.exists() else []
    positions = _positions_from_state(root) if root.exists() else []
    signals = _load_signals(root) if root.exists() else []
    trade_intents = _load_trade_intents(root) if root.exists() else []
    scorecards, excerpt, report_warnings = _parse_daily_scorecards(root) if root.exists() else ([], [], [])
    warnings.extend(report_warnings)
    if any(not p.paper_only for p in processes):
        warnings.append("unexpected_non_paper_process")
    kill_switch = _kill_switch_status(root) if root.exists() else "unknown"
    reconcile_payload = _load_reconcile(root) if root.exists() else {"reconcile": {}, "watchdog": {}}
    default_closed = ([], Decimal("0"), 0, None, [], [], Decimal("0"), 0, None)
    recent_trades, realized_pnl, closed_count, winrate, performance_points, strategy_performance, legacy_pnl, legacy_count, legacy_winrate = _load_closed_trades(root) if root.exists() else default_closed
    reconcile = reconcile_payload.get("reconcile") if isinstance(reconcile_payload.get("reconcile"), dict) else {}
    watchdog = reconcile_payload.get("watchdog") if isinstance(reconcile_payload.get("watchdog"), dict) else {}
    live_allowed = False
    mode = "paper_only" if processes or signals or positions else "offline_or_no_runtime"
    exposure = sum((_to_decimal(p.notional_usd) for p in positions), Decimal("0"))
    unrealized = sum((_to_decimal(p.unrealized_pnl_usd) for p in positions), Decimal("0"))
    portfolio = HyperliquidPortfolioSnapshot(
        mode=mode,
        live_allowed=live_allowed,
        kill_switch_status=kill_switch,
        open_positions=positions,
        recent_closed_trades=recent_trades,
        risk_checks=_risk_checks(root, processes, reconcile_payload, kill_switch) if root.exists() else [],
        performance_points=performance_points,
        strategy_performance=strategy_performance,
        warning_explanations=_warning_explanations(warnings, reconcile_payload),
        exposure_notional_usd=_fmt_money(exposure),
        unrealized_pnl_usd=_fmt_money(unrealized),
        realized_pnl_usd=_fmt_money(realized_pnl),
        closed_trades_count=closed_count,
        winrate_pct=winrate,
        experimental_legacy_realized_pnl_usd=_fmt_money(legacy_pnl),
        experimental_legacy_closed_trades_count=legacy_count,
        experimental_legacy_winrate_pct=legacy_winrate,
        free_usdc=str(reconcile.get("free_usdc")) if reconcile.get("free_usdc") is not None else None,
        equity_usdc=str(reconcile.get("equity")) if reconcile.get("equity") is not None else None,
        margin_usage_pct=str(reconcile.get("margin_usage_pct") or watchdog.get("margin_usage_pct")) if reconcile.get("margin_usage_pct") is not None or watchdog.get("margin_usage_pct") is not None else None,
        open_orders_count=int(_to_decimal(len(reconcile.get("open_orders") or []) if isinstance(reconcile.get("open_orders"), list) else watchdog.get("open_orders_count") or 0)),
        stops_missing_count=int(_to_decimal(reconcile.get("stops_missing_count") or watchdog.get("stops_missing_count") or 0)),
        reconcile_status=str(reconcile.get("status") or watchdog.get("status") or "unknown"),
    )
    status = CryptoTraderStatus(
        bridge_status="ok" if root.exists() else "missing_runtime",
        runtime_root=str(root),
        source="CryptoTradingBot runtime read-only files",
        generated_at=_now(),
        mode=mode,
        live_allowed=live_allowed,
        safety_summary="Read-only bridge: no orders, no signing, no config mutation. Dashboard may approve intents later; CryptoTrader must re-check gates.",
        processes=processes,
        open_positions_count=len(positions),
        signals_count=len(signals),
        scorecards_count=len(scorecards),
        warnings=sorted(set(warnings)),
    )
    return CryptoTraderSnapshot(status=status, portfolio=portfolio, signals=signals, trade_intents=trade_intents, scorecards=scorecards, daily_report_excerpt=excerpt)


def get_crypto_trader_status() -> CryptoTraderStatus:
    return build_crypto_trader_snapshot().status


def get_hyperliquid_portfolio() -> HyperliquidPortfolioSnapshot:
    return build_crypto_trader_snapshot().portfolio


def get_crypto_trader_signals() -> list[CryptoTraderSignal]:
    return build_crypto_trader_snapshot().signals


def get_crypto_trader_scorecards() -> list[CryptoTraderScorecard]:
    return build_crypto_trader_snapshot().scorecards


def get_trade_intents() -> list[TradeIntent]:
    return build_crypto_trader_snapshot().trade_intents


def record_trade_approval(request: TradeApprovalRequest) -> TradeApprovalResponse:
    decision = request.decision.strip().lower()
    if decision not in VALID_APPROVAL_DECISIONS:
        return TradeApprovalResponse(
            status="rejected",
            approval_id="invalid-decision",
            intent_id=request.intent_id,
            decision=decision,
            message="Ungültige Entscheidung. Erlaubt: approve, reject, paper_only.",
        )
    if not request.confirm:
        return TradeApprovalResponse(
            status="preview",
            approval_id="preview-only",
            intent_id=request.intent_id,
            decision=decision,
            message="Preview: Noch nicht gespeichert. confirm=true senden, um die Entscheidung zu auditieren. Keine Order-Ausführung.",
        )
    intents = {intent.intent_id: intent for intent in get_trade_intents()}
    intent = intents.get(request.intent_id)
    if intent is None:
        return TradeApprovalResponse(
            status="rejected",
            approval_id="unknown-intent",
            intent_id=request.intent_id,
            decision=decision,
            message="TradeIntent nicht in der aktuellen read-only Queue gefunden; keine Entscheidung gespeichert.",
        )
    created_at = _now()
    approval_id = _approval_id(request.intent_id, decision, created_at)
    path = _approval_log_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    payload = {
        "approval_id": approval_id,
        "intent_id": request.intent_id,
        "decision": decision,
        "note": request.note,
        "created_at": created_at,
        "portfolio_id": intent.portfolio_id,
        "source_engine": intent.source_engine,
        "strategy_id": intent.strategy_id,
        "coin": intent.coin,
        "side": intent.side,
        "signal": intent.signal,
        "intent_created_at": intent.created_at,
        "execution_allowed": False,
        "boundary": "FinanceManager approval audit only; CryptoTrader must re-check all gates before any execution.",
    }
    with path.open("a", encoding="utf-8") as fh:
        fh.write(json.dumps(payload, ensure_ascii=False, sort_keys=True) + "\n")
    return TradeApprovalResponse(
        status="recorded",
        approval_id=approval_id,
        intent_id=request.intent_id,
        decision=decision,
        message="Entscheidung auditiert. Keine Order ausgeführt; CryptoTrader muss Gates später separat prüfen.",
        execution_allowed=False,
        audit_path=str(path),
    )
