# src/calibration/strategy_promotion.py
from typing import List, Dict, Any, Optional
import math


def _clamp(v, lo, hi):
    return max(lo, min(hi, v))


def _score_return(oos_return):
    return _clamp((oos_return / 50.0) * 30.0, 0, 30)


def _score_stability(positive_rate, std_return):
    score = positive_rate * 15
    score += _clamp(10 - std_return, 0, 10)
    return _clamp(score, 0, 25)


def _score_risk(worst_dd):
    return _clamp((1 + worst_dd / 20.0) * 20.0, 0, 20)


def _score_trades(avg_trades):
    return _clamp(avg_trades / 10.0 * 10.0, 0, 10)


def _score_regime(regime_detail: Dict[str, Any]):
    if not regime_detail:
        return 0
    positives = sum(1 for r in regime_detail.values() if r.get("avg_return", 0) >= 0)
    total = len(regime_detail)
    return (positives / total) * 15 if total else 0


def _extract_window_returns(strategy: Dict[str, Any]) -> List[float]:
    windows = strategy.get("windows", []) or []
    out = []

    for w in windows:
        if not isinstance(w, dict):
            continue

        v = w.get("return_pct", None)
        if v is None:
            continue

        try:
            out.append(float(v))
        except (TypeError, ValueError):
            continue

    return out


def _pearson_corr(a: List[float], b: List[float]) -> Optional[float]:
    n = min(len(a), len(b))
    if n < 2:
        return None

    x = a[:n]
    y = b[:n]

    mx = sum(x) / n
    my = sum(y) / n

    num = 0.0
    den_x = 0.0
    den_y = 0.0

    for xi, yi in zip(x, y):
        dx = xi - mx
        dy = yi - my
        num += dx * dy
        den_x += dx * dx
        den_y += dy * dy

    if den_x <= 0 or den_y <= 0:
        return None

    return num / math.sqrt(den_x * den_y)


def evaluate_strategy(strategy: Dict[str, Any], config: Dict[str, Any]):
    metrics = strategy.get("diagnostics", {}) or {}
    stability = metrics.get("stability", {}) or {}
    trades = metrics.get("trades", {}) or {}
    regimes = metrics.get("regimes", {}).get("performance", {}).get("detail", {}) or {}

    oos_return = float(strategy.get("oos_total_return_pct", 0) or 0)
    worst_dd = float(strategy.get("oos_max_dd_worst_pct", -100) or -100)
    positive_rate = float(stability.get("positive_window_rate", 0) or 0)
    std_return = float(stability.get("std_return_pct", 0) or 0)
    avg_trades = float(trades.get("avg_trades_per_window", 0) or 0)

    score = 0
    score += _score_return(oos_return)
    score += _score_stability(positive_rate, std_return)
    score += _score_risk(worst_dd)
    score += _score_trades(avg_trades)
    score += _score_regime(regimes)

    promote_thr = config.get("promote_score_threshold", 70)
    review_thr = config.get("review_score_threshold", 55)

    if score >= promote_thr:
        status = "promote"
    elif score >= review_thr:
        status = "review"
    else:
        status = "reject"

    return {
        "strategy_id": strategy.get("strategy_id"),
        "score": round(score, 2),
        "status": status,
        "diversity_blocked_by": None,
        "diversity_correlation": None,
    }


def _apply_diversity_filter(
    ranked: List[Dict[str, Any]],
    original_map: Dict[str, Dict[str, Any]],
    max_corr: float,
) -> List[Dict[str, Any]]:
    selected_promotes: List[Dict[str, Any]] = []

    for item in ranked:
        if item.get("status") != "promote":
            continue

        strategy_id = item.get("strategy_id")
        current_strategy = original_map.get(strategy_id, {})
        current_returns = _extract_window_returns(current_strategy)

        blocked = False
        blocked_by = None
        blocked_corr = None

        for selected in selected_promotes:
            selected_id = selected.get("strategy_id")
            selected_strategy = original_map.get(selected_id, {})
            selected_returns = _extract_window_returns(selected_strategy)

            corr = _pearson_corr(current_returns, selected_returns)
            if corr is None:
                continue

            if abs(corr) >= max_corr:
                blocked = True
                blocked_by = selected_id
                blocked_corr = round(corr, 4)
                break

        if blocked:
            item["status"] = "review_diversity"
            item["diversity_blocked_by"] = blocked_by
            item["diversity_correlation"] = blocked_corr
        else:
            selected_promotes.append(item)

    return ranked


def rank_strategies(strategies: List[Dict[str, Any]], config: Dict[str, Any]):
    evaluated = [evaluate_strategy(s, config) for s in strategies]
    evaluated.sort(key=lambda x: x["score"], reverse=True)

    for i, e in enumerate(evaluated):
        e["rank"] = i + 1

    max_corr = float(config.get("max_strategy_correlation", 0.85) or 0.85)
    original_map = {s.get("strategy_id"): s for s in strategies if s.get("strategy_id")}

    evaluated = _apply_diversity_filter(evaluated, original_map, max_corr=max_corr)
    return evaluated