from __future__ import annotations

import json
import time
from pathlib import Path
from typing import Any

import config_manager
try:
    from src.runtime_trade_guard import cooldown_remaining_sec, same_side_reentry_veto
    from src.market_context import is_defensive_regime, is_trend_regime, normalize_regime_label
except ModuleNotFoundError:
    from runtime_trade_guard import cooldown_remaining_sec, same_side_reentry_veto
    from market_context import is_defensive_regime, is_trend_regime, normalize_regime_label


def _load_runtime_open_guard(project_root: Path) -> dict[str, Any]:
    """Read the authoritative runtime_open_guard.json written by pipeline."""
    path = project_root / "data" / "runtime_open_guard.json"
    if not path.exists():
        return {}
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    return payload if isinstance(payload, dict) else {}


def _coerce_float(value: Any, default: float) -> float:
    try:
        return float(value)
    except (TypeError, ValueError):
        return default


def _resolve_configured_base_lots(project_root: Path) -> float:
    configured = config_manager.get_effective_param("base_lots", 0.05)
    return max(0.01, _coerce_float(configured, 0.05))


def _resolve_rule_cap(project_root: Path, *, regime: str = "") -> tuple[float, list[str]]:
    """Regime-aware position cap.

    TREND regimes: loss-streak caps are relaxed (trends reward conviction).
    RANGING/TRANSITION: tight caps (ranges punish overcommitment).
    """
    state = _load_runtime_open_guard(project_root)
    reasons: list[str] = []

    hard_cap = _coerce_float(config_manager.get_effective_param("max_open_lots", 1.0), 1.0)
    hard_cap = max(0.01, hard_cap)

    # ── Regime-aware multiplier ──
    reg = normalize_regime_label(regime)
    if reg in ("STRONG_TREND",):
        regime_cap_mult = 2.5   # Strong trend: 2.5x the base loss-streak cap
    elif reg in ("WEAK_TREND", "STEALTH_TREND"):
        regime_cap_mult = 1.8   # Weak trend: 1.8x
    elif reg == "RANGING":
        regime_cap_mult = 1.0   # Ranging: baseline
    elif is_defensive_regime(reg):
        regime_cap_mult = 0.7   # Dangerous/uncertain: tighter than baseline
    else:
        regime_cap_mult = 1.0   # UNKNOWN: baseline

    consecutive_losses = int(state.get("consecutive_losses", 0) or 0)
    if consecutive_losses >= 3:
        base_streak_cap = 0.03 * regime_cap_mult
        hard_cap = min(hard_cap, base_streak_cap)
        reasons.append(f"LOSS_STREAK_CAP(regime={reg},mult={regime_cap_mult:.1f}x)")
    elif consecutive_losses >= 2:
        base_streak_cap = 0.05 * regime_cap_mult
        hard_cap = min(hard_cap, base_streak_cap)
        reasons.append(f"LOSS_PRESSURE_CAP(regime={reg},mult={regime_cap_mult:.1f}x)")

    # ── Session-mode risk scaling ──
    try:
        from bridge.session_analyzer import get_current_session
        session = get_current_session()
        if session == "ASIA":
            # Read session profile from agent_state (written by pipeline)
            session_mode = str((state.get("current_session_profile", {}) or {}).get("mode", "NORMAL") or "NORMAL").upper()
            if session_mode == "ASIA_NOISE":
                hard_cap = min(hard_cap, 0.03)
                reasons.append("SESSION:ASIA_NOISE")
            elif session_mode == "ASIA_DEFENSIVE":
                hard_cap = min(hard_cap, 0.05)
                reasons.append("SESSION:ASIA_DEFENSIVE")
    except Exception:
        pass

    return max(0.0, hard_cap), reasons


def resolve_live_volume_plan(requested_volume: float, *, project_root: Path, regime: str = "") -> dict[str, Any]:
    requested = max(0.0, _coerce_float(requested_volume, 0.0))
    configured_base_lots = _resolve_configured_base_lots(project_root)
    rule_cap_lots, rule_cap_reasons = _resolve_rule_cap(project_root, regime=regime)
    effective_request = requested if requested > 0 else configured_base_lots
    final_volume_lots = min(effective_request, configured_base_lots, rule_cap_lots or effective_request)
    return {
        "requested_volume_lots": round(requested, 4),
        "configured_base_lots": round(configured_base_lots, 4),
        "rule_cap_lots": round(rule_cap_lots, 4),
        "rule_cap_reasons": list(rule_cap_reasons),
        "final_volume_lots": round(max(0.0, final_volume_lots), 4),
    }


def resolve_live_open_guard(*, project_root: Path, side: str, cooldown_adjustment_sec: int = 0, strategy_id: str = "", regime: str = "") -> dict[str, Any]:
    guard_state = _load_runtime_open_guard(project_root)
    state = guard_state
    if not state:
        return {
            "allow_open": False,
            "reason": "RUNTIME_STATE_MISSING",
            "cooldown_remaining_sec": 0,
            "same_side_reentry_remaining_sec": 0,
        }
    # ── 多仓位支持：检查实际持仓数 vs max_open_positions ──
    positions_all = state.get("positions_all", []) if isinstance(state.get("positions_all", []), list) else []
    open_count = len([p for p in positions_all if isinstance(p, dict) and p.get("ticket", 0)])
    # 兼容旧标量字段：如果 positions_all 为空但 position_open=True，回退到旧逻辑
    if open_count <= 0 and bool(state.get("position_open", False)):
        ticket = int(state.get("position_ticket", 0) or 0)
        entry = float(state.get("position_entry", 0) or 0)
        if ticket <= 0 and entry <= 0:
            pos_time = float(state.get("position_time", 0) or 0)
            if pos_time <= 0 or (time.time() - pos_time) > 120:
                return {
                    "allow_open": True,
                    "reason": "STALE_POSITION_OVERRIDE",
                    "cooldown_remaining_sec": 0,
                    "same_side_reentry_remaining_sec": 0,
                }
        open_count = 1  # 标量模式：视为1个持仓
    # ── 策略维度持仓计数 ──
    # 两种来源：_position_strategy_map（多策略并发）或 position_strategy_id（单策略兼容）
    pos_strategy_map = state.get("_position_strategy_map", {})
    if isinstance(pos_strategy_map, dict) and pos_strategy_map:
        same_strategy_open = sum(
            1 for sid in pos_strategy_map.values()
            if str(sid or "").strip() == strategy_id
        )
    else:
        # 兼容旧标量字段
        pos_sid = str(state.get("position_strategy_id", "") or "").strip()
        same_strategy_open = 1 if pos_sid and strategy_id and pos_sid == strategy_id and open_count > 0 else 0

    if open_count > 0:
        max_open = int(_coerce_float(config_manager.get_effective_param("max_open_positions",
                       config_manager.get_effective_param("max_positions", 4)), 4))
        if open_count >= max_open:
            return {
                "allow_open": False,
                "reason": f"MAX_POSITIONS_REACHED:{open_count}>={max_open}",
                "cooldown_remaining_sec": 0,
                "same_side_reentry_remaining_sec": 0,
            }
        # ── 同策略仓位上限 ──
        if strategy_id and same_strategy_open > 0:
            max_per_strat = int(_coerce_float(
                config_manager.get_effective_param("max_positions_per_strategy", 1), 1))
            if same_strategy_open >= max_per_strat:
                return {
                    "allow_open": False,
                    "reason": f"STRATEGY_POSITION_LIMIT:{strategy_id}:{same_strategy_open}>={max_per_strat}",
                    "cooldown_remaining_sec": 0,
                    "same_side_reentry_remaining_sec": 0,
                }
    incident_controls = (
        state.get("incident_controls", {})
        if isinstance(state.get("incident_controls", {}), dict)
        else {}
    )
    base_cooldown = int(_coerce_float(config_manager.get_effective_param("post_close_cooldown_sec", 300), 300))
    configured_cooldown = max(30, base_cooldown + cooldown_adjustment_sec)

    # Per-strategy skip: if the last close was from a DIFFERENT strategy,
    # only apply a weak 60s global cooldown instead of full post_close_cooldown.
    if strategy_id:
        per_strat_times = state.get("_strategy_close_times", {})
        if isinstance(per_strat_times, dict) and strategy_id not in per_strat_times:
            configured_cooldown = min(configured_cooldown, 60)

    remaining = cooldown_remaining_sec(
        state,
        cooldown_sec=configured_cooldown,
        regime=regime,
        strategy_id=strategy_id,
    )
    if remaining > 0:
        return {
            "allow_open": False,
            "reason": "POST_CLOSE_COOLDOWN",
            "cooldown_remaining_sec": int(remaining),
            "same_side_reentry_remaining_sec": 0,
        }

    same_side_remaining, same_side_reason = same_side_reentry_veto(state, side, strategy_id=strategy_id)
    if same_side_remaining > 0:
        return {
            "allow_open": False,
            "reason": same_side_reason or "SAME_SIDE_REENTRY_LOCK",
            "cooldown_remaining_sec": 0,
            "same_side_reentry_remaining_sec": int(same_side_remaining),
        }

    return {
        "allow_open": True,
        "reason": "",
        "cooldown_remaining_sec": 0,
        "same_side_reentry_remaining_sec": 0,
    }
