from __future__ import annotations

from typing import Any
from src.market_context import normalize_session_bucket


CANONICAL_STRATEGY_REGISTRY: dict[str, dict[str, Any]] = {
    "ema_macd": {
        "style": "trend",
        "aliases": ["EMAMacdSignalGenerator"],
        "applicable_regimes": ["STRONG_TREND", "WEAK_TREND", "STEALTH_TREND"],
        "applicable_sessions": ["LONDON", "NEW_YORK", "OVERLAP"],
        "min_regime_confidence": {"STRONG_TREND": 0.55, "WEAK_TREND": 0.60, "STEALTH_TREND": 0.55},
        "countertrend_allowed": False,
        "market_family_tags": ["TREND"],
        "market_phase_tags": ["TREND_CONTINUATION", "TREND_PULLBACK"],
        "entry_family_tags": ["trend"],
        "signal_archetype": "momentum_continuation",
        "structure_tags": ["EMA_STACK", "MACD_CONFIRM"],
        "session_tags": ["LONDON", "NEW_YORK", "OVERLAP"],
        "volatility_tags": ["NORMAL", "HIGH"],
        "direction_mode": "FOLLOW_TREND",
        "min_market_confidence": 0.55,
    },
    "adx_breakout": {
        "style": "trend",
        "aliases": ["ADXBreakoutSignal"],
        "applicable_regimes": ["STRONG_TREND", "WEAK_TREND"],
        "applicable_sessions": ["LONDON", "NEW_YORK", "OVERLAP"],
        "min_regime_confidence": {"STRONG_TREND": 0.60, "WEAK_TREND": 0.72},
        "countertrend_allowed": False,
        "market_family_tags": ["TREND"],
        "market_phase_tags": ["BREAKOUT_READY", "TREND_CONTINUATION"],
        "entry_family_tags": ["trend"],
        "signal_archetype": "breakout",
        "structure_tags": ["ADX_EXPANSION", "BREAKOUT_READY"],
        "session_tags": ["LONDON", "NEW_YORK", "OVERLAP"],
        "volatility_tags": ["NORMAL", "HIGH"],
        "direction_mode": "FOLLOW_TREND",
        "min_market_confidence": 0.58,
    },
    "wyckoff": {
        "style": "trend",
        "aliases": ["wyckoff_v1"],
        "applicable_regimes": ["STRONG_TREND", "WEAK_TREND"],
        "applicable_sessions": ["LONDON", "NEW_YORK", "OVERLAP"],
        "min_regime_confidence": {"STRONG_TREND": 0.55, "WEAK_TREND": 0.60},
        "countertrend_allowed": False,
        "market_family_tags": ["TREND"],
        "market_phase_tags": ["TREND_CONTINUATION", "BREAKOUT_READY"],
        "entry_family_tags": ["trend"],
        "signal_archetype": "structure_breakout",
        "structure_tags": ["STRUCTURE_BREAK", "ACCUMULATION_BREAK"],
        "session_tags": ["LONDON", "NEW_YORK", "OVERLAP"],
        "volatility_tags": ["NORMAL", "HIGH"],
        "direction_mode": "FOLLOW_TREND",
        "min_market_confidence": 0.55,
    },
    "composite": {
        "style": "hybrid",
        "aliases": ["composite_v1"],
        "applicable_regimes": ["WEAK_TREND", "RANGING", "TRANSITION"],
        "applicable_sessions": ["ALL"],
        "min_regime_confidence": 0.40,
        "countertrend_allowed": True,
        "market_family_tags": ["TRANSITION"],
        "market_phase_tags": ["TRANSITION"],
        "entry_family_tags": ["hold"],
        "signal_archetype": "hybrid_blend",
        "structure_tags": ["MULTI_FACTOR"],
        "session_tags": ["ALL"],
        "volatility_tags": ["LOW", "NORMAL", "HIGH"],
        "direction_mode": "HYBRID",
        "min_market_confidence": 0.45,
    },
    "pullback_fade": {
        "style": "trend",
        "aliases": ["PullbackFadeSignal"],
        "applicable_regimes": ["STRONG_TREND", "WEAK_TREND", "STEALTH_TREND"],
        "applicable_sessions": ["LONDON", "NEW_YORK", "OVERLAP"],
        "min_regime_confidence": {"STRONG_TREND": 0.55, "WEAK_TREND": 0.60, "STEALTH_TREND": 0.55},
        "countertrend_allowed": False,
        "market_family_tags": ["TREND"],
        "market_phase_tags": ["TREND_PULLBACK"],
        "entry_family_tags": ["trend"],
        "signal_archetype": "trend_pullback",
        "structure_tags": ["PULLBACK", "RETRACE_FADE"],
        "session_tags": ["LONDON", "NEW_YORK", "OVERLAP"],
        "volatility_tags": ["NORMAL", "HIGH"],
        "direction_mode": "FOLLOW_TREND",
        "min_market_confidence": 0.58,
    },
    "rsi_extreme": {
        "style": "mean_reversion",
        "aliases": ["RSIExtremeSignal"],
        "applicable_regimes": ["RANGING", "WEAK_TREND", "TRANSITION"],
        "applicable_sessions": ["ASIA", "ALL"],
        "min_regime_confidence": {"RANGING": 0.45, "WEAK_TREND": 0.50, "TRANSITION": 0.50},
        "countertrend_allowed": True,
        "market_family_tags": ["RANGE"],
        "market_phase_tags": ["OSCILLATION", "RANGE_EXTREME"],
        "entry_family_tags": ["range"],
        "signal_archetype": "extreme_reversion",
        "structure_tags": ["RSI_EXTREME", "MEAN_REVERSION"],
        "session_tags": ["ASIA", "ALL"],
        "volatility_tags": ["LOW", "NORMAL"],
        "direction_mode": "MEAN_REVERT",
        "min_market_confidence": 0.45,
    },
    "boll_band": {
        "style": "mean_reversion",
        "aliases": ["BollBandSignal"],
        "applicable_regimes": ["RANGING", "WEAK_TREND", "TRANSITION"],
        "applicable_sessions": ["ASIA", "ALL"],
        "min_regime_confidence": {"RANGING": 0.45, "WEAK_TREND": 0.50, "TRANSITION": 0.50},
        "countertrend_allowed": True,
        "market_family_tags": ["RANGE"],
        "market_phase_tags": ["OSCILLATION", "RANGE_EXTREME"],
        "entry_family_tags": ["range"],
        "signal_archetype": "band_reversion",
        "structure_tags": ["BAND_EDGE", "VOL_COMPRESSION"],
        "session_tags": ["ASIA", "ALL"],
        "volatility_tags": ["LOW", "NORMAL"],
        "direction_mode": "MEAN_REVERT",
        "min_market_confidence": 0.45,
    },
    "grid_osc": {
        "style": "mean_reversion",
        "aliases": ["GridOscSignal"],
        "applicable_regimes": ["RANGING"],
        "applicable_sessions": ["ASIA", "ALL"],
        "min_regime_confidence": 0.55,
        "countertrend_allowed": True,
        "market_family_tags": ["RANGE"],
        "market_phase_tags": ["OSCILLATION"],
        "entry_family_tags": ["range"],
        "signal_archetype": "grid_oscillation",
        "structure_tags": ["NARROW_RANGE", "GRID_CHANNEL"],
        "session_tags": ["ASIA", "ALL"],
        "volatility_tags": ["LOW"],
        "direction_mode": "MEAN_REVERT",
        "min_market_confidence": 0.55,
    },
    "key_level": {
        "style": "mean_reversion",
        "aliases": ["KeyLevelSignal"],
        "applicable_regimes": ["RANGING", "WEAK_TREND", "TRANSITION"],
        "applicable_sessions": ["ALL"],
        "min_regime_confidence": {"RANGING": 0.45, "WEAK_TREND": 0.50, "TRANSITION": 0.50},
        "countertrend_allowed": True,
        "market_family_tags": ["RANGE"],
        "market_phase_tags": ["OSCILLATION", "RANGE_EXTREME"],
        "entry_family_tags": ["range"],
        "signal_archetype": "level_rejection",
        "structure_tags": ["KEY_LEVEL", "REJECTION"],
        "session_tags": ["ALL"],
        "volatility_tags": ["LOW", "NORMAL"],
        "direction_mode": "MEAN_REVERT",
        "min_market_confidence": 0.45,
    },
    
}

SESSION_NOISE_BLOCK = {"ASIA_NOISE", "ASIA_DEFENSIVE"}
STRATEGY_TYPE_PRIORITY: dict[str, int] = {
    "trend": 3,
    "mean_reversion": 2,
    "hybrid": 1,
}
TIMEFRAME_PRIORITY: dict[str, int] = {
    "D1": 5,
    "H4": 4,
    "H1": 3,
    "M15": 2,
    "M5": 1,
}
LLM_TO_REGIME: dict[str, str] = {
    "TRENDING": "WEAK_TREND",
    "STRONG_TREND": "STRONG_TREND",
    "WEAK_TREND": "WEAK_TREND",
    "FAKE_TREND": "FAKE_TREND",
    "STEALTH_TREND": "STEALTH_TREND",
    "RANGING": "RANGING",
    "SHOCK_REPRICING": "VOLATILITY_SPIKE",
    "UNCERTAIN": "TRANSITION",
    "BREAKOUT": "STRONG_TREND",
    "CONSOLIDATION": "RANGING",
}


def _normalize_name(value: Any) -> str:
    return str(value or "").strip().lower()


def canonical_strategy_id(strategy_id: Any) -> str:
    raw = _normalize_name(strategy_id)
    if not raw:
        return "unknown"
    for canonical, spec in CANONICAL_STRATEGY_REGISTRY.items():
        aliases = {_normalize_name(alias) for alias in spec.get("aliases", [])}
        aliases.add(_normalize_name(canonical))
        if raw in aliases:
            return canonical
    if raw.endswith("_v1") and raw[:-3] in CANONICAL_STRATEGY_REGISTRY:
        return raw[:-3]
    if raw.endswith("_v2") and raw[:-3] in CANONICAL_STRATEGY_REGISTRY:
        return raw[:-3]
    return raw


def get_strategy_runtime_contract(strategy_id: Any) -> dict[str, Any]:
    canonical = canonical_strategy_id(strategy_id)
    spec = CANONICAL_STRATEGY_REGISTRY.get(canonical, {})
    raw_min_conf = spec.get("min_regime_confidence", 0.0) or 0.0
    if isinstance(raw_min_conf, dict):
        scalar_min_conf = float(raw_min_conf.get("DEFAULT") or next(iter(raw_min_conf.values()), 0.0) or 0.0)
    else:
        scalar_min_conf = float(raw_min_conf)
    raw_market_conf = spec.get("min_market_confidence", scalar_min_conf) or scalar_min_conf
    if isinstance(raw_market_conf, dict):
        scalar_market_conf = float(raw_market_conf.get("DEFAULT") or next(iter(raw_market_conf.values()), 0.0) or 0.0)
    else:
        scalar_market_conf = float(raw_market_conf)
    return {
        "strategy_id": canonical,
        "strategy_style": str(spec.get("style", "hybrid") or "hybrid"),
        "applicable_regimes": list(spec.get("applicable_regimes", ["UNKNOWN"])),
        "applicable_sessions": list(spec.get("applicable_sessions", ["ALL"])),
        "min_regime_confidence": scalar_min_conf,
        "min_regime_confidence_per_regime": raw_min_conf if isinstance(raw_min_conf, dict) else {},
        "countertrend_allowed": bool(spec.get("countertrend_allowed", False)),
        "market_family_tags": list(spec.get("market_family_tags", [])),
        "market_phase_tags": list(spec.get("market_phase_tags", [])),
        "entry_family_tags": list(spec.get("entry_family_tags", [])),
        "signal_archetype": str(spec.get("signal_archetype", "") or ""),
        "structure_tags": list(spec.get("structure_tags", [])),
        "session_tags": list(spec.get("session_tags", spec.get("applicable_sessions", ["ALL"]))),
        "volatility_tags": list(spec.get("volatility_tags", [])),
        "direction_mode": str(spec.get("direction_mode", "HYBRID") or "HYBRID"),
        "min_market_confidence": scalar_market_conf,
    }


def strategy_style(strategy_id: Any) -> str:
    return str(get_strategy_runtime_contract(strategy_id).get("strategy_style", "hybrid") or "hybrid")


def strategy_route_family_alignment(strategy_id: Any, route_family: Any) -> float:
    family = str(route_family or "").strip().lower()
    if not family:
        return 0.5
    contract = get_strategy_runtime_contract(strategy_id)
    style = str(contract.get("strategy_style", "hybrid") or "hybrid").strip().lower()
    allowed_regimes = {
        str(item or "").strip().upper()
        for item in contract.get("applicable_regimes", [])
        if str(item).strip()
    }
    market_tags = {
        str(item or "").strip().upper()
        for item in contract.get("market_family_tags", [])
        if str(item).strip()
    }
    if family == "range":
        # Pure category-based block: Trend strategies NEVER trade in Range route
        if style == "trend":
            return 0.0
        if bool({"RANGE", "RANGING", "LOW_VOLATILITY"} & market_tags) or style in {"mean_reversion", "reversion"}:
            return 1.0
        if style == "hybrid":
            return 0.45
        return 0.0
    if family == "trend":
        # Pure category-based block: Mean reversion NEVER trades in Trend route
        if style == "mean_reversion":
            return 0.0
        if bool({"TREND", "TRENDING"} & market_tags) or style in {"trend", "breakout", "momentum"}:
            return 1.0
        if style == "hybrid":
            return 0.45
        return 0.0
    if family == "uncertain":
        if style == "hybrid" or "TRANSITION" in market_tags or "TRANSITION" in allowed_regimes:
            return 1.0
        if style in {"mean_reversion", "reversion"} and bool({"RANGE", "RANGING", "LOW_VOLATILITY"} & market_tags):
            return 0.85
        if style in {"trend", "breakout", "momentum"} and bool({"TREND", "TRENDING"} & market_tags):
            return 0.8
        return 0.0
    if family == "blocked":
        return 0.0
    if family == "range_loosening":
        # Cautious activation: trend strategies get partial access (0.6),
        # mean_reversion still appropriate (0.8), hybrid gets 0.7
        if style == "trend":
            return 0.6
        if style in {"mean_reversion", "reversion"}:
            return 0.8
        if style == "hybrid":
            return 0.7
        return 0.5
    return 0.5


def strategy_type_priority_for_route(strategy_id: Any, route_family: Any = "") -> int:
    style = strategy_style(strategy_id)
    family = str(route_family or "").strip().lower()
    if family == "range":
        return {
            "mean_reversion": 4,
            "hybrid": 3,
            "trend": 2,
        }.get(style, 2)
    if family == "trend":
        return {
            "trend": 4,
            "hybrid": 3,
            "mean_reversion": 1,
        }.get(style, 2)
    if family == "uncertain":
        return {
            "hybrid": 3,
            "mean_reversion": 3,
            "trend": 2,
        }.get(style, 2)
    return STRATEGY_TYPE_PRIORITY.get(style, 1)


def is_strategy_route_family_compatible(strategy_id: Any, route_family: Any) -> tuple[bool, str]:
    family = str(route_family or "").strip().lower()
    if not family:
        return True, ""
    if family == "blocked":
        return False, "route_blocked"
    alignment = strategy_route_family_alignment(strategy_id, family)
    if family in {"trend", "range"} and alignment < 1.0:
        return False, f"route_family_mismatch:{family}"
    if family == "uncertain" and alignment < 0.8:
        return False, "route_family_mismatch:uncertain"
    return True, ""


def _get_min_regime_confidence(contract: dict[str, Any], regime_label: str) -> float:
    """Return per-regime min confidence if configured, else fall back to scalar."""
    per_regime = contract.get("min_regime_confidence_per_regime")
    if isinstance(per_regime, dict):
        return float(per_regime.get(regime_label, per_regime.get("DEFAULT", 0.0)) or 0.0)
    raw = contract.get("min_regime_confidence", 0.0) or 0.0
    return float(raw)


def is_strategy_regime_compatible(
    strategy_id: Any,
    regime: Any,
    *,
    regime_confidence: float = 1.0,
    session_mode: str = "",
    route_family: str = "",
) -> tuple[bool, str]:
    contract = get_strategy_runtime_contract(strategy_id)
    regime_label = str(regime or "UNKNOWN").strip().upper()
    allowed_regimes = {str(item or "").strip().upper() for item in contract.get("applicable_regimes", [])}
    allowed_sessions = {str(item or "").strip().upper() for item in contract.get("applicable_sessions", [])}
    route_ok, route_reason = is_strategy_route_family_compatible(strategy_id, route_family)
    if not route_ok:
        return False, f"runtime_route_mismatch:{route_reason}"
    if regime_label and allowed_regimes and regime_label not in allowed_regimes:
        return False, f"runtime_regime_mismatch:{regime_label}"
    if regime_label not in {"TRANSITION"} or not allowed_regimes or "TRANSITION" not in allowed_regimes:
        min_conf = _get_min_regime_confidence(contract, regime_label)
        if float(regime_confidence or 0.0) < min_conf:
            return False, f"runtime_regime_confidence_low:{float(regime_confidence or 0.0):.2f}"
    session_label = str(session_mode or "").strip().upper()
    session_bucket = normalize_session_bucket(session_label) if session_label else ""
    normalized_allowed_sessions = {normalize_session_bucket(item) for item in allowed_sessions}
    if (
        session_label
        and session_label != "ALL"
        and session_bucket
        and "ALL" not in normalized_allowed_sessions
        and normalized_allowed_sessions
        and session_bucket not in normalized_allowed_sessions
    ):
        return False, f"runtime_session_mismatch:{session_label}"
    if regime_label in {"FAKE_TREND", "VOLATILITY_SPIKE", "EXTREME"}:
        return False, f"runtime_garbage_regime:{regime_label}"

    # --- Enhanced Session-Regime Guard for Trend Strategies ---
    is_trend_strat = strategy_style(strategy_id) == "trend"
    transition_ready_trend = is_trend_strat and "TRANSITION" in allowed_regimes
    if is_trend_strat:
        # Strict block for Asia unless specifically allowed (which we just removed from trend strats)
        if session_bucket == "ASIA":
            # Extra safety: even if session_label isn't in NOISE_BLOCK, 
            # we block trend in Asia unless it's a STRONG_TREND with high confidence.
            if regime_label != "STRONG_TREND" or float(regime_confidence or 0.0) < 0.85:
                return False, f"runtime_trend_blocked_in_asia:{session_label}:{regime_label}"
        
        # Original noise block check
        if session_label in SESSION_NOISE_BLOCK:
            return False, f"runtime_session_noise:{session_label}"

    if (
        regime_label in {"RANGING", "TRANSITION"}
        and is_trend_strat
        and float(regime_confidence or 0.0) < 0.7
        and not (regime_label == "TRANSITION" and transition_ready_trend)
    ):
        return False, f"runtime_trend_not_preferred:{regime_label}"
    return True, ""


def list_compatible_strategies(
    regime: Any,
    *,
    regime_confidence: float = 1.0,
    session_mode: str = "",
    route_family: str = "",
    brain=None,
    min_win_rate: float = 0.35,
) -> list[str]:
    compatible: list[str] = []
    for strategy_id in CANONICAL_STRATEGY_REGISTRY.keys():
        ok, _reason = is_strategy_regime_compatible(
            strategy_id,
            regime,
            regime_confidence=regime_confidence,
            session_mode=session_mode,
            route_family=route_family,
        )
        if ok:
            compatible.append(strategy_id)
    if brain is not None:
        filtered = []
        for sid in compatible:
            stats = brain.get_strategy_regime_stats(sid, regime)
            if stats.get("reliable") and stats["win_rate"] < min_win_rate:
                continue
            filtered.append(sid)
        compatible = filtered if filtered else compatible
    return compatible

