from __future__ import annotations

import time
from typing import Any

try:
    from src.market_context import is_defensive_regime, normalize_regime_label
except ModuleNotFoundError:
    from market_context import is_defensive_regime, normalize_regime_label


def build_profit_sense_cooldown_profile(
    state: dict[str, Any] | None, regime: str = ""
) -> dict[str, Any]:
    """
    Compress recent exit quality into a reusable cooldown profile so bridge and
    pipeline openings share the same post-close pacing rules.
    """
    profile = {
        "bias_label": "NEUTRAL",
        "cooldown_add_sec": 0,
        "cooldown_multiplier": 1.0,
        "allow_fast_reentry": True,
        "floor_sec": 60,
        "consecutive_losses": int((state or {}).get("consecutive_losses", 0) or 0),
        "opportunity_state": "UNKNOWN",
        "profit_capture_ratio": 0.0,
        "profit_giveback_atr": 0.0,
        "peak_profit_atr": 0.0,
        "momentum_decay_score": 0.0,
    }
    if not isinstance(state, dict):
        return profile

    telemetry = state.get("trailing_telemetry", {})
    if not isinstance(telemetry, dict):
        telemetry = {}
    rows = state.get("recent_closures", [])
    if not isinstance(rows, list):
        rows = []
    last_row = rows[-1] if rows and isinstance(rows[-1], dict) else {}

    opportunity_state = str(
        last_row.get("opportunity_state") or telemetry.get("opportunity_state") or "UNKNOWN"
    ).upper()
    capture_ratio = float(
        last_row.get("profit_capture_ratio", telemetry.get("profit_capture_ratio", 0.0)) or 0.0
    )
    giveback_atr = float(
        last_row.get("profit_giveback_atr", telemetry.get("profit_giveback_atr", 0.0)) or 0.0
    )
    peak_profit_atr = float(
        last_row.get("peak_profit_atr", telemetry.get("peak_profit_atr", 0.0)) or 0.0
    )
    momentum_decay_score = float(
        last_row.get("momentum_decay_score", telemetry.get("momentum_decay_score", 0.0)) or 0.0
    )
    last_pnl = float(last_row.get("pnl", 0.0) or 0.0) if last_row else 0.0
    consecutive_losses = int(profile["consecutive_losses"])

    profile.update(
        {
            "opportunity_state": opportunity_state,
            "profit_capture_ratio": round(capture_ratio, 4),
            "profit_giveback_atr": round(giveback_atr, 4),
            "peak_profit_atr": round(peak_profit_atr, 4),
            "momentum_decay_score": round(momentum_decay_score, 4),
        }
    )

    cooldown_add_sec = 0
    cooldown_multiplier = 1.0
    allow_fast_reentry = True
    bias_label = "NEUTRAL"

    if last_pnl < 0 and consecutive_losses > 0:
        cooldown_add_sec += min(15 * 60, consecutive_losses * 3 * 60)
        cooldown_multiplier = max(cooldown_multiplier, 1.0 + min(0.60, consecutive_losses * 0.16))
        allow_fast_reentry = False
        bias_label = "LOSS_STREAK"

    # Regime-aware cooldown: ranging/transition markets need extra patience
    reg = normalize_regime_label(regime)
    if reg in {"RANGING", "TRANSITION"}:
        cooldown_add_sec += 3 * 60
        cooldown_multiplier = max(cooldown_multiplier, 1.3)
        allow_fast_reentry = False
        if bias_label == "NEUTRAL":
            bias_label = "RANGE_PAUSE"

    # Consecutive-loss surcharge varies by regime
    if consecutive_losses > 0:
        if reg in {"RANGING", "TRANSITION"}:
            cooldown_add_sec += consecutive_losses * 2 * 60
        elif reg in {"STRONG_TREND", "WEAK_TREND", "STEALTH_TREND"}:
            cooldown_add_sec += consecutive_losses * 1 * 60

    if opportunity_state in {"LOSS_EXPANDING", "LOSS_STABILIZING"}:
        cooldown_add_sec += 3 * 60
        cooldown_multiplier = max(cooldown_multiplier, 1.18)
        allow_fast_reentry = False
        bias_label = "LOSS_PRESSURE"

    if peak_profit_atr >= 1.0 and capture_ratio <= 0.42:
        cooldown_add_sec += 4 * 60
        cooldown_multiplier = max(cooldown_multiplier, 1.22)
        allow_fast_reentry = False
        bias_label = "CAPTURE_FAILURE"

    if opportunity_state in {"PROFIT_GIVEBACK", "PROFIT_EXHAUSTING"} and giveback_atr >= 0.45:
        cooldown_add_sec += 3 * 60
        cooldown_multiplier = max(cooldown_multiplier, 1.18)
        allow_fast_reentry = False
        bias_label = "PROFIT_GIVEBACK"

    if momentum_decay_score >= 0.65 and capture_ratio <= 0.55 and peak_profit_atr >= 0.8:
        cooldown_add_sec += 2 * 60
        cooldown_multiplier = max(cooldown_multiplier, 1.12)
        allow_fast_reentry = False
        if bias_label == "NEUTRAL":
            bias_label = "MOMENTUM_DECAY"

    if (
        bias_label == "NEUTRAL"
        and peak_profit_atr >= 1.0
        and capture_ratio >= 0.78
        and consecutive_losses <= 0
    ):
        cooldown_multiplier = min(cooldown_multiplier, 0.88)
        bias_label = "HEALTHY_CAPTURE"

    profile.update(
        {
            "bias_label": bias_label,
            "cooldown_add_sec": int(max(0, cooldown_add_sec)),
            "cooldown_multiplier": float(max(0.75, min(1.8, cooldown_multiplier))),
            "allow_fast_reentry": bool(allow_fast_reentry),
            "floor_sec": 2 * 60 if not allow_fast_reentry else 60,
        }
    )
    return profile


def cooldown_remaining_sec(
    state: dict[str, Any] | None,
    *,
    cooldown_sec: int,
    now_ts: float | None = None,
    strategy_id: str = "",
    regime: str = "",
) -> int:
    required_cooldown = int(max(30, cooldown_sec or 0))
    # ── Regime-aware cooldown: trend markets allow faster re-entry ──
    reg = normalize_regime_label(regime)
    if reg in ("STRONG_TREND", "WEAK_TREND", "STEALTH_TREND"):
        required_cooldown = int(required_cooldown * 0.5)   # Half the cooldown in trends
    elif reg == "RANGING":
        required_cooldown = int(required_cooldown * 1.5)    # 50% longer in ranging markets
    elif reg in ("TRANSITION", "FAKE_TREND", "VOLATILITY_SPIKE"):
        required_cooldown = int(required_cooldown * 2.0)    # Double in uncertain/defensive markets
    elif is_defensive_regime(reg):
        required_cooldown = int(required_cooldown * 1.5)    # 50% longer in dangerous markets
    required_cooldown = max(15, required_cooldown)          # Never below 15s
    profit_profile = build_profit_sense_cooldown_profile(state, regime=reg)
    state_dict = state if isinstance(state, dict) else {}
    now_value = float(now_ts if now_ts is not None else time.time())
    try:
        rows = state_dict.get("recent_closures", [])
        if isinstance(rows, list) and rows:
            last = rows[-1] if isinstance(rows[-1], dict) else {}
            last_pnl = float(last.get("pnl", 0.0) or 0.0)
            risk_tier = str(last.get("counter_trend_risk", "LOW") or "LOW").upper()
            if last_pnl < 0:
                required_cooldown = int(min(12 * 60, max(required_cooldown, 7 * 60)))
            elif last_pnl > 0 and risk_tier not in {"LOW", "MEDIUM"}:
                required_cooldown = int(max(required_cooldown, 2 * 60))
    except Exception:
        pass

    required_cooldown = int(
        required_cooldown * float(profit_profile.get("cooldown_multiplier", 1.0) or 1.0)
    )
    required_cooldown += int(profit_profile.get("cooldown_add_sec", 0) or 0)
    required_cooldown = max(int(profit_profile.get("floor_sec", 60) or 60), required_cooldown)
    # Progressive cooldown cap based on consecutive losses — no more 25min hard limit
    consec = int((state_dict or {}).get("consecutive_losses", 0) or 0)
    if consec >= 5:
        max_cooldown = 120 * 60  # 2 hours
    elif consec >= 4:
        max_cooldown = 60 * 60   # 1 hour
    elif consec >= 3:
        max_cooldown = 30 * 60   # 30 min
    elif consec >= 2:
        max_cooldown = 15 * 60   # 15 min
    else:
        max_cooldown = 10 * 60   # 10 min
    required_cooldown = min(max_cooldown, required_cooldown)

    # * 使用 max(三个源) 避免单一字段缺失导致冷却归零
    rows = state_dict.get("recent_closures", [])
    last_row_ts = float(rows[-1].get("ts", 0.0) or 0.0) if isinstance(rows, list) and rows and isinstance(rows[-1], dict) else 0.0
    last_close_time = max(
        float(state_dict.get("last_close_time", 0.0) or 0.0),
        float(state_dict.get("last_closed_ts", 0.0) or 0.0),
        last_row_ts,
    )

    # Multi-strategy: use the strategy-specific close timestamp when available.
    # A recent close from strategy A must not stall strategy B.
    if strategy_id:
        per_strat_times = state_dict.get("_strategy_close_times", {})
        if isinstance(per_strat_times, dict):
            strat_close_ts = float(per_strat_times.get(strategy_id, 0.0) or 0.0)
            if strat_close_ts > 0:
                last_close_time = strat_close_ts
        last_closed_strat = state_dict.get("last_closed_strategy", "")
        if last_closed_strat and last_closed_strat != strategy_id:
            return 0
    if last_close_time <= 0:
        return 0
    elapsed = now_value - last_close_time
    if elapsed >= required_cooldown:
        return 0
    return int(max(0, required_cooldown - elapsed))


def same_side_reentry_veto(
    state: dict[str, Any] | None,
    side: str,
    *,
    now_ts: float | None = None,
    vol_ratio: float = 1.0,
    strategy_id: str = "",
) -> tuple[int, str]:
    if not isinstance(state, dict):
        return 0, ""
    side_upper = str(side or "").upper()
    if side_upper not in {"BUY", "SELL"}:
        return 0, ""

    now_value = float(now_ts if now_ts is not None else time.time())

    # ── Level 1: Per-strategy cooldown (main check) ──
    if strategy_id:
        per_strat_times = state.get("_strategy_close_times", {})
        per_strat_sides = state.get("_strategy_close_sides", {})
        if isinstance(per_strat_times, dict) and isinstance(per_strat_sides, dict):
            strat_close_ts = float(per_strat_times.get(strategy_id, 0) or 0)
            strat_close_side = str(per_strat_sides.get(strategy_id, "") or "").upper()
            if strat_close_ts > 0 and strat_close_side == side_upper:
                elapsed = now_value - strat_close_ts
                strat_lock = min(600, int(120 * max(0.5, min(2.0, vol_ratio))))
                if elapsed < strat_lock:
                    return int(strat_lock - elapsed), f"SAME_STRAT_SAME_SIDE_LOCK:{strategy_id}"
        last_closed_strat = str(state.get("last_closed_strategy", "") or "")
        if last_closed_strat and last_closed_strat != strategy_id:
            return 0, ""

    # ── Level 2: Global cooldown (weak — reduced to 15s when per-strategy tracking active) ──
    rows = state.get("recent_closures", [])
    if not isinstance(rows, list):
        rows = []
    last_row = rows[-1] if rows and isinstance(rows[-1], dict) else {}
    last_side = str(last_row.get("side") or state.get("last_closed_side", "") or "").upper()
    if last_side != side_upper:
        return 0, ""

    # When per-strategy _strategy_close_times already provides accurate tracking
    # (Level 1 handled it above), only apply a minimal 15s global backstop.
    # If per-strategy tracking is absent, use 30s as a reasonable fallback.
    has_per_strat_tracking = bool(
        isinstance(state.get("_strategy_close_times", {}), dict)
        and state.get("_strategy_close_times")
    )
    global_lock = 15 if has_per_strat_tracking else 30  # reduced from 60
    last_close_ts = max(
        float(state.get("last_close_time", 0.0) or 0.0),
        float(state.get("last_closed_ts", 0.0) or 0.0),
        float(last_row.get("ts", 0.0) or 0.0),
    )
    elapsed = now_value - last_close_ts
    if elapsed >= global_lock:
        return 0, ""
    remaining = int(global_lock - elapsed)
    return remaining, "GLOBAL_SAME_SIDE_LOCK"
