from __future__ import annotations

import time
from dataclasses import dataclass, field
from typing import Any, Dict, List

import config_manager


TIER_NORMAL = "NORMAL"
TIER_DEFENSIVE = "DEFENSIVE"
TIER_SURVIVAL = "SURVIVAL"
TIER_BLOCKED = "BLOCKED"

_TIER_ORDER = {
    TIER_NORMAL: 0,
    TIER_DEFENSIVE: 1,
    TIER_SURVIVAL: 2,
    TIER_BLOCKED: 3,
}

_EXTREME_REGIMES = {"EXTREME", "VOLATILITY_SPIKE"}
_DEFENSIVE_REGIMES = {"RANGING", "RANGE", "SIDEWAYS", "TRANSITION", "UNCERTAIN"}
_STABLE_REGIMES = {"STRONG_TREND", "WEAK_TREND", "STEALTH_TREND", "TREND"}
_OLD_SHRINK_KEYS = (
    "_shrink_applied",
    "_shrink_cap",
    "_shrink_reasons",
    "_shrink_telemetry",
    "_shrink_params_adapted",
)


@dataclass
class PositionTierContext:
    strategy_id: str = ""
    current_tier: str = TIER_NORMAL
    regime: str = "UNKNOWN"
    runtime_state: Dict[str, Any] = field(default_factory=dict)
    consecutive_losses: int = 0
    daily_realized_loss_pct: float = 0.0
    floating_pnl_pct: float = 0.0
    spread_anomaly: bool = False
    circuit_open: bool = False
    execution_degraded: bool = False
    macro_blocked: bool = False


@dataclass
class PositionTierDecision:
    tier: str
    target_lots: float
    reason_code: str
    reason_text: str
    active_rules: List[str] = field(default_factory=list)
    recovery_needed: List[str] = field(default_factory=list)
    recovery_progress: Dict[str, float] = field(default_factory=dict)


def _normalize_tier(value: Any) -> str:
    tier = str(value or TIER_NORMAL).upper().strip()
    if tier in _TIER_ORDER:
        return tier
    return TIER_NORMAL


def _safe_float(value: Any, default: float = 0.0) -> float:
    try:
        return float(value or 0.0)
    except (TypeError, ValueError):
        return default


def _effective_rows(runtime_state: Dict[str, Any], strategy_id: str = "") -> tuple[list[dict], bool]:
    rows = runtime_state.get("recent_closures", []) if isinstance(runtime_state, dict) else []
    if not isinstance(rows, list):
        rows = []
    valid_rows: list[dict] = []
    for row in rows:
        if not isinstance(row, dict):
            continue
        try:
            _safe_float(row.get("pnl", 0.0))
        except Exception:
            continue
        valid_rows.append(row)
    valid_rows = valid_rows[-40:]
    strategy_key = str(strategy_id or "").strip()
    if not strategy_key:
        return valid_rows, False
    strategy_rows = [
        row
        for row in valid_rows
        if str(row.get("strategy_id", "") or "").strip() == strategy_key
    ]
    if strategy_rows:
        return strategy_rows[-40:], False
    return valid_rows, bool(valid_rows)


def _win_rate(rows: list[dict]) -> float:
    meaningful = [row for row in rows if _safe_float(row.get("pnl", 0.0)) != 0.0]
    if not meaningful:
        return 0.0
    wins = sum(1 for row in meaningful if _safe_float(row.get("pnl", 0.0)) > 0.0)
    return wins / max(1, len(meaningful))


def _loss_count(rows: list[dict], lookback: int) -> int:
    recent = rows[-lookback:] if lookback > 0 else rows
    return sum(1 for row in recent if _safe_float(row.get("pnl", 0.0)) < 0.0)


def _non_loss_streak(rows: list[dict]) -> int:
    streak = 0
    for row in reversed(rows):
        if _safe_float(row.get("pnl", 0.0)) >= 0.0:
            streak += 1
            continue
        break
    return streak


def _stable_regime(regime: str) -> bool:
    return str(regime or "UNKNOWN").upper() in _STABLE_REGIMES


def _tier_lot_map() -> dict[str, float]:
    min_lot = max(0.01, _safe_float(config_manager.get_effective_param("min_lot", 0.01), 0.01))
    normal = max(min_lot, _safe_float(config_manager.get_effective_param("position_tier_normal_lots", 0.05), 0.05))
    defensive = max(min_lot, _safe_float(config_manager.get_effective_param("position_tier_defensive_lots", 0.03), 0.03))
    survival = max(min_lot, _safe_float(config_manager.get_effective_param("position_tier_survival_lots", min_lot), min_lot))
    defensive = min(defensive, normal)
    survival = min(survival, defensive)
    return {
        TIER_NORMAL: round(normal, 2),
        TIER_DEFENSIVE: round(defensive, 2),
        TIER_SURVIVAL: round(survival, 2),
        TIER_BLOCKED: 0.0,
    }


def _warmup_sample_floor() -> int:
    return max(
        3,
        int(_safe_float(config_manager.get_effective_param("position_tier_warmup_min_samples", 3), 3)),
    )


def _defensive_recovery_sample_floor() -> int:
    configured_floor = int(
        _safe_float(config_manager.get_effective_param("position_tier_recovery_min_samples", 3), 3)
    )
    return max(3, configured_floor)


def _survival_recovery(rows: list[dict], regime: str) -> tuple[list[str], dict[str, float], bool]:
    needed = [
        "连续2笔真实平仓非亏损",
        "市场退出极端波动",
    ]
    non_loss = _non_loss_streak(rows)
    stable_market = 1.0 if str(regime or "").upper() not in _EXTREME_REGIMES else 0.0
    progress = {
        "non_loss_streak": float(min(non_loss, 2)),
        "target_non_loss_streak": 2.0,
        "market_stable": stable_market,
    }
    return needed, progress, non_loss >= 2 and stable_market >= 1.0


def _defensive_recovery(rows: list[dict], regime: str, strategy_samples: int) -> tuple[list[str], dict[str, float], bool]:
    recovery_sample_floor = _defensive_recovery_sample_floor()
    needed = [
        "连续2笔真实平仓非亏损",
        f"当前策略真实样本>={recovery_sample_floor}",
        "市场退出过渡或震荡",
    ]
    non_loss = _non_loss_streak(rows)
    stable_market = 1.0 if _stable_regime(regime) else 0.0
    progress = {
        "non_loss_streak": float(min(non_loss, 2)),
        "target_non_loss_streak": 2.0,
        "strategy_samples": float(strategy_samples),
        "target_strategy_samples": float(recovery_sample_floor),
        "market_stable": stable_market,
    }
    stable_or_transition = stable_market >= 1.0 or str(regime or "UNKNOWN").upper() in _DEFENSIVE_REGIMES
    can_recover = (
        non_loss >= 2
        or strategy_samples >= recovery_sample_floor
        or (stable_or_transition and non_loss >= 1)
    )
    return needed, progress, can_recover


def evaluate_position_tier(context: PositionTierContext) -> PositionTierDecision:
    tier_lots = _tier_lot_map()
    regime = str(context.regime or "UNKNOWN").upper()
    current_tier = _normalize_tier(context.current_tier)
    rows, used_fallback = _effective_rows(context.runtime_state, context.strategy_id)
    strategy_samples = len(rows)
    strategy_win_rate = _win_rate(rows)
    recent_loss_count = _loss_count(rows, 3)
    warmup_sample_floor = _warmup_sample_floor()
    min_sample_floor = int(_safe_float(config_manager.get_effective_param("position_tier_min_samples", 5), 5))
    active_rules: list[str] = []

    if context.macro_blocked:
        return PositionTierDecision(
            tier=TIER_BLOCKED,
            target_lots=tier_lots[TIER_BLOCKED],
            reason_code="MACRO_BLOCKED",
            reason_text="宏观风险窗口，暂停新开仓",
            active_rules=["macro_blocked"],
        )
    if context.circuit_open:
        return PositionTierDecision(
            tier=TIER_BLOCKED,
            target_lots=tier_lots[TIER_BLOCKED],
            reason_code="CIRCUIT_OPEN",
            reason_text="系统熔断开启，暂停新开仓",
            active_rules=["circuit_open"],
        )
    if context.execution_degraded:
        return PositionTierDecision(
            tier=TIER_BLOCKED,
            target_lots=tier_lots[TIER_BLOCKED],
            reason_code="EXECUTION_DEGRADED",
            reason_text="执行链路异常，暂停新开仓",
            active_rules=["execution_degraded"],
        )
    if context.spread_anomaly:
        return PositionTierDecision(
            tier=TIER_BLOCKED,
            target_lots=tier_lots[TIER_BLOCKED],
            reason_code="SPREAD_ANOMALY",
            reason_text="点差异常，暂停新开仓",
            active_rules=["spread_anomaly"],
        )

    desired_tier = TIER_NORMAL
    reason_code = "NORMAL_READY"
    reason_text = "系统状态正常，允许正常仓位"

    if regime in _EXTREME_REGIMES:
        desired_tier = TIER_SURVIVAL
        reason_code = "EXTREME_REGIME"
        reason_text = "市场处于极端波动，切换生存仓位"
        active_rules.append(f"regime:{regime}")
    elif context.consecutive_losses >= 3:
        desired_tier = TIER_SURVIVAL
        reason_code = "LOSS_STREAK_SURVIVAL"
        reason_text = "连续亏损达到生存阈值，切换最小仓位"
        active_rules.append(f"consecutive_losses:{context.consecutive_losses}")
    elif context.daily_realized_loss_pct >= _safe_float(
        config_manager.get_effective_param("position_tier_daily_loss_survival_pct", 3.0), 3.0
    ):
        desired_tier = TIER_SURVIVAL
        reason_code = "DAILY_LOSS_SURVIVAL"
        reason_text = "当日已实现亏损偏大，切换生存仓位"
        active_rules.append(f"daily_realized_loss_pct:{round(context.daily_realized_loss_pct, 4)}")
    elif recent_loss_count >= 2:
        desired_tier = TIER_DEFENSIVE
        reason_code = "RECENT_LOSS_DEFENSIVE"
        reason_text = "最近真实平仓连续受挫，切换防守仓位"
        active_rules.append(f"recent_loss_count:{recent_loss_count}")
    elif strategy_samples < warmup_sample_floor:
        desired_tier = TIER_DEFENSIVE
        reason_code = "STRATEGY_WARMUP"
        reason_text = "当前策略真实样本不足，维持防守仓位"
        active_rules.append(f"strategy_samples:{strategy_samples}")
    elif regime in _DEFENSIVE_REGIMES:
        desired_tier = TIER_DEFENSIVE
        reason_code = "TRANSITION_REGIME"
        reason_text = "市场处于过渡或震荡阶段，切换防守仓位"
        active_rules.append(f"regime:{regime}")
    elif strategy_samples >= min_sample_floor and strategy_win_rate < _safe_float(
        config_manager.get_effective_param("position_tier_min_win_rate", 0.45), 0.45
    ):
        desired_tier = TIER_DEFENSIVE
        reason_code = "LOW_WIN_RATE"
        reason_text = "当前策略近期命中率偏低，切换防守仓位"
        active_rules.append(f"strategy_win_rate:{round(strategy_win_rate, 4)}")

    if used_fallback:
        active_rules.append("strategy_fallback")
        if desired_tier != TIER_NORMAL and "fallback" not in reason_text.lower():
            reason_text = f"{reason_text}，当前使用公共样本口径"

    recovery_needed: list[str] = []
    recovery_progress: dict[str, float] = {}
    final_tier = desired_tier

    if _TIER_ORDER[desired_tier] > _TIER_ORDER[current_tier]:
        final_tier = desired_tier
    elif current_tier == TIER_SURVIVAL and desired_tier in {TIER_DEFENSIVE, TIER_NORMAL}:
        recovery_needed, recovery_progress, can_recover = _survival_recovery(rows, regime)
        if can_recover:
            final_tier = TIER_DEFENSIVE
            reason_code = "SURVIVAL_RECOVERING"
            reason_text = "生存仓位恢复中，先回到防守仓位"
        else:
            final_tier = TIER_SURVIVAL
            reason_code = "SURVIVAL_RECOVERY_PENDING"
            reason_text = "生存仓位恢复条件未满足，继续保持最小仓位"
    elif current_tier == TIER_DEFENSIVE and desired_tier == TIER_NORMAL:
        recovery_needed, recovery_progress, can_recover = _defensive_recovery(rows, regime, strategy_samples)
        if can_recover:
            final_tier = TIER_NORMAL
            reason_code = "DEFENSIVE_RECOVERED"
            reason_text = "防守仓位恢复条件满足，回到正常仓位"
        else:
            final_tier = TIER_DEFENSIVE
            reason_code = "DEFENSIVE_RECOVERY_PENDING"
            reason_text = "防守仓位恢复条件未满足，继续保持防守仓位"

    target_lots = tier_lots[final_tier]
    if final_tier == TIER_SURVIVAL and not recovery_needed:
        recovery_needed, recovery_progress, _ = _survival_recovery(rows, regime)
    elif final_tier == TIER_DEFENSIVE and not recovery_needed:
        recovery_needed, recovery_progress, _ = _defensive_recovery(rows, regime, strategy_samples)

    return PositionTierDecision(
        tier=final_tier,
        target_lots=target_lots,
        reason_code=reason_code,
        reason_text=reason_text,
        active_rules=active_rules,
        recovery_needed=recovery_needed,
        recovery_progress=recovery_progress,
    )


def apply_position_tier_state(runtime_state: Dict[str, Any], decision: PositionTierDecision) -> Dict[str, Any]:
    if not isinstance(runtime_state, dict):
        runtime_state = {}
    now_ts = time.time()
    for key in _OLD_SHRINK_KEYS:
        runtime_state.pop(key, None)
    previous_tier = _normalize_tier(runtime_state.get("position_tier", TIER_NORMAL))
    runtime_state["position_tier"] = decision.tier
    runtime_state["tier_target_lots"] = round(float(decision.target_lots or 0.0), 4)
    runtime_state["tier_reason_code"] = decision.reason_code
    runtime_state["tier_reason_text"] = decision.reason_text
    runtime_state["tier_active_rules"] = list(decision.active_rules)
    runtime_state["tier_recovery_needed"] = list(decision.recovery_needed)
    runtime_state["tier_recovery_progress"] = dict(decision.recovery_progress)
    runtime_state["tier_updated_ts"] = now_ts
    if previous_tier != decision.tier:
        runtime_state["tier_since_ts"] = now_ts
        runtime_state["tier_last_transition"] = f"{previous_tier}->{decision.tier}"
    elif float(runtime_state.get("tier_since_ts", 0.0) or 0.0) <= 0.0:
        runtime_state["tier_since_ts"] = now_ts
    return runtime_state
