"""Centralized fail-safes before any strategy places new risk."""

from __future__ import annotations

import json
from datetime import date, datetime
from pathlib import Path
from typing import Any

from RenTech.live.entry_ledger import count_entries_submitted_today
from RenTech.live.platform_config import PlatformConfig, StrategySlot
from RenTech.live.protocols import PortfolioSnapshot, RiskAction, RiskVerdict


def _read_kill_switch(path: Path) -> bool:
    if not path.is_file():
        return False
    text = path.read_text(encoding="utf-8").strip().upper()
    return text in ("1", "HALT", "STOP", "KILL", "YES", "TRUE")


def _day_state_path(state_dir: Path) -> Path:
    return state_dir / "platform_day.json"


def _load_day_state(state_dir: Path, today: date) -> dict:
    p = _day_state_path(state_dir)
    if not p.is_file():
        return {}
    try:
        data = json.loads(p.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return {}
    if not isinstance(data, dict):
        return {}
    if str(data.get("date")) != today.isoformat():
        return {}
    return data


def _save_day_state(state_dir: Path, today: date, payload: dict) -> None:
    state_dir.mkdir(parents=True, exist_ok=True)
    p = _day_state_path(state_dir)
    body = {"date": today.isoformat(), **payload}
    tmp = p.with_suffix(".tmp")
    tmp.write_text(json.dumps(body, indent=2) + "\n", encoding="utf-8")
    tmp.replace(p)


def _update_peak_nav(state_dir: Path, today: date, nav: float) -> float:
    day = _load_day_state(state_dir, today)
    opening = float(day.get("opening_nav_usd", nav)) if day else nav
    peak = float(day.get("peak_nav_usd", nav)) if day else nav
    if nav > peak:
        peak = nav
    if not day:
        _save_day_state(
            state_dir,
            today,
            {"opening_nav_usd": nav, "peak_nav_usd": peak},
        )
    else:
        _save_day_state(
            state_dir,
            today,
            {"opening_nav_usd": opening, "peak_nav_usd": peak},
        )
    return peak


def count_spy_option_positions(portfolio: PortfolioSnapshot) -> int:
    """Count non-flat SPY option legs (proxy for open VRP structures)."""
    return sum(
        1
        for p in portfolio.positions
        if p.symbol == "SPY" and p.sec_type == "OPT" and abs(p.position) >= 1e-9
    )


def _parse_hhmm(hhmm: str) -> tuple[int, int]:
    parts = str(hhmm).strip().split(":", 1)
    if len(parts) != 2:
        raise ValueError(f"expected HH:MM, got {hhmm!r}")
    return int(parts[0]), int(parts[1])


def evaluate_platform_risk(
    cfg: PlatformConfig,
    portfolio: PortfolioSnapshot,
    today: date,
    *,
    recommend_only: bool,
    ib: Any | None = None,
) -> RiskVerdict:
    verdict = RiskVerdict(action=RiskAction.ALLOW, allow_exits=True, allow_new_entries=True)
    rk = cfg.risk

    if recommend_only or rk.recommend_only:
        verdict.merge_block_entries("recommend_only mode (no live orders)")
        return verdict

    if rk.halt_on_kill_switch and _read_kill_switch(cfg.kill_switch_path):
        verdict.action = RiskAction.HALT_ALL
        verdict.allow_new_entries = False
        verdict.allow_exits = True
        verdict.reasons.append(f"kill switch active: {cfg.kill_switch_path}")
        return verdict

    nav = portfolio.net_liquidation_usd
    if nav < rk.min_net_liq_usd:
        verdict.action = RiskAction.HALT_ALL
        verdict.allow_new_entries = False
        verdict.allow_exits = True
        verdict.reasons.append(f"NetLiq ${nav:,.2f} below min ${rk.min_net_liq_usd:,.2f}")

    if rk.min_excess_liquidity_usd is not None:
        excess = portfolio.excess_liquidity_usd
        if excess < rk.min_excess_liquidity_usd:
            verdict.merge_block_entries(
                f"ExcessLiquidity ${excess:,.2f} below min ${rk.min_excess_liquidity_usd:,.2f}"
            )

    if portfolio.margin_utilization > rk.max_margin_utilization:
        verdict.merge_block_entries(
            f"margin utilization {portfolio.margin_utilization:.1%} > cap {rk.max_margin_utilization:.1%}"
        )

    peak = _update_peak_nav(cfg.state_dir, today, nav)
    if rk.max_drawdown_pct_from_peak is not None and peak > 0:
        dd_frac = (peak - nav) / peak
        if dd_frac >= rk.max_drawdown_pct_from_peak:
            verdict.merge_block_entries(
                f"drawdown from peak {dd_frac:.2%} >= cap {rk.max_drawdown_pct_from_peak:.2%} "
                f"(peak=${peak:,.2f} nav=${nav:,.2f})"
            )

    day = _load_day_state(cfg.state_dir, today)
    opening = float(day.get("opening_nav_usd", nav)) if day else nav
    if not day:
        _save_day_state(cfg.state_dir, today, {"opening_nav_usd": nav, "peak_nav_usd": nav})

    day_pnl = nav - opening
    if rk.max_daily_loss_usd is not None and day_pnl <= -abs(rk.max_daily_loss_usd):
        verdict.merge_block_entries(
            f"daily loss ${-day_pnl:,.2f} exceeds max_daily_loss_usd ${rk.max_daily_loss_usd:,.2f}"
        )
    if rk.max_daily_loss_pct_nav is not None and opening > 0:
        loss_pct = -day_pnl / opening
        if day_pnl < 0 and loss_pct >= rk.max_daily_loss_pct_nav:
            verdict.merge_block_entries(
                f"daily loss {loss_pct:.2%} exceeds max_daily_loss_pct_nav {rk.max_daily_loss_pct_nav:.2%}"
            )

    if rk.block_if_pending_orders and ib is not None:
        from RenTech.live.ibkr_session import has_pending_orders

        if has_pending_orders(ib):
            verdict.merge_block_entries("pending IB orders on account")

    return verdict


def strategy_capital_budget(slot: StrategySlot, portfolio: PortfolioSnapshot) -> float:
    mode = slot.capital_budget_mode.lower()
    nav = portfolio.net_liquidation_usd
    if mode == "nav_pct":
        pct = slot.capital_budget_nav_pct if slot.capital_budget_nav_pct is not None else 0.0
        return nav * pct
    if mode == "fixed_usd":
        return float(slot.capital_budget_usd or 0.0)
    if mode == "full_nav":
        return nav
    raise ValueError(f"Unknown capital_budget_mode {mode!r} for strategy {slot.id}")


def evaluate_strategy_risk(
    slot: StrategySlot,
    portfolio: PortfolioSnapshot,
    platform: RiskVerdict,
    *,
    capital_budget_usd: float,
    state_dir: Path,
    today: date,
    now_et: datetime,
    entry_after_et: str,
    force_entry_now: bool = False,
) -> RiskVerdict:
    verdict = RiskVerdict(
        action=platform.action,
        reasons=list(platform.reasons),
        allow_exits=platform.allow_exits,
        allow_new_entries=platform.allow_new_entries,
    )
    if not platform.allow_new_entries:
        return verdict

    if not slot.enabled_for_entries:
        verdict.merge_block_entries(f"strategy {slot.id}: enabled_for_entries=false")

    submitted = count_entries_submitted_today(state_dir, slot.id, today)
    if submitted >= slot.max_new_entries_per_day:
        verdict.merge_block_entries(
            f"strategy {slot.id}: daily entries {submitted}/{slot.max_new_entries_per_day} already submitted"
        )

    spy_opts = count_spy_option_positions(portfolio)
    if spy_opts >= slot.max_open_positions:
        verdict.merge_block_entries(
            f"strategy {slot.id}: SPY option legs {spy_opts} >= max_open_positions {slot.max_open_positions}"
        )

    if not force_entry_now:
        hh, mm = _parse_hhmm(entry_after_et)
        after_gate = now_et.replace(hour=hh, minute=mm, second=0, microsecond=0)
        if now_et < after_gate:
            verdict.merge_block_entries(
                f"strategy {slot.id}: before entry_after_et {entry_after_et} ET"
            )
        if slot.entry_before_et:
            bh, bm = _parse_hhmm(slot.entry_before_et)
            before_gate = now_et.replace(hour=bh, minute=bm, second=0, microsecond=0)
            if now_et > before_gate:
                verdict.merge_block_entries(
                    f"strategy {slot.id}: after entry_before_et {slot.entry_before_et} ET"
                )

    if capital_budget_usd <= 0:
        verdict.merge_block_entries(f"strategy {slot.id}: zero capital budget")
    elif capital_budget_usd > portfolio.net_liquidation_usd * 1.05:
        verdict.merge_block_entries(
            f"strategy {slot.id}: budget ${capital_budget_usd:,.0f} exceeds NAV"
        )
    return verdict
