"""capital_allocator.py — Dynamic capital allocation for multi-strategy portfolios.

Implements a Kelly-variant allocation model:

    per_strategy_allocation =
        base_weight
        × sharpe_multiplier
        × win_rate_multiplier
        × correlation_penalty

All inputs are bounded to prevent extreme allocations.
Designed to be called once per bar from the pipeline.

Usage::

    from src.capital_allocator import allocate

    weights = allocate(strategies, regime_profile, performance_stats)
"""
from __future__ import annotations
from typing import Any


# ── Bounds ────────────────────────────────────────────────────────

MIN_ALLOCATION = 0.005   # minimum lot size (0.005 = half a micro lot)
MAX_ALLOCATION = 0.06    # hard cap on single-strategy allocation
DEFAULT_BASE_WEIGHT = 0.02
MAX_TOTAL_EXPOSURE = 0.10  # hard cap across all strategies


def allocate(
    strategies: list[dict],
    regime_profile: dict[str, float],
    performance: dict[str, dict] | None = None,
    *,
    total_capital: float = 10_000.0,
    max_total_exposure: float = MAX_TOTAL_EXPOSURE,
) -> dict[str, Any]:
    """Allocate capital across active strategies.

    Args:
        strategies: List of active strategy descriptors.
            Each must have: {strategy_id, strategy_type, base_weight}
        regime_profile: Weight fractions from regime_router.
            e.g. {"trend": 0.70, "mean_reversion": 0.20, "sentinel": 0.10}
        performance: Optional per-strategy performance stats.
            Each value: {sharpe_20d, win_rate_20d}

    Returns:
        dict with keys:
          allocations     — {strategy_id: lot_size}
          total_exposure  — sum of all allocations
          capped          — list of capped strategies
          meta            — allocation reasoning
    """
    if not strategies:
        return {"allocations": {}, "total_exposure": 0.0, "capped": [], "meta": {"reason": "no_strategies"}}

    perf = performance or {}
    allocations: dict[str, float] = {}
    total = 0.0
    capped: list[str] = []

    for s in strategies:
        sid = str(s.get("strategy_id", "unknown") or "unknown")
        stype = str(s.get("strategy_type", "hybrid") or "hybrid")

        # ── Step 1: Base weight from regime profile ──
        base = float(s.get("base_weight", DEFAULT_BASE_WEIGHT) or DEFAULT_BASE_WEIGHT)
        regime_mult = float(regime_profile.get(stype, 0.10) or 0.10)
        weight = base * regime_mult

        # ── Step 2: Performance multipliers ──
        pstat = perf.get(sid, {}) if isinstance(perf, dict) else {}

        # Sharpe: scale 0.2–2.0× around baseline 1.5
        sharpe_20d = float(pstat.get("sharpe_20d", 1.0) or 1.0)
        sharpe_mult = max(0.2, min(2.0, sharpe_20d / 1.5))

        # Win rate: scale 0.3–1.5× around baseline 0.50
        wr_20d = float(pstat.get("win_rate_20d", 0.50) or 0.50)
        wr_mult = max(0.3, min(1.5, wr_20d / 0.50))

        weight *= sharpe_mult * wr_mult

        # ── Step 3: Correlation penalty ──
        correlation = float(pstat.get("avg_correlation", 0.0) or 0.0)
        corr_penalty = 1.0 - abs(correlation) * 0.3
        weight *= corr_penalty

        # ── Step 4: Bounds ──
        if weight > MAX_ALLOCATION:
            capped.append(f"{sid}:{weight:.4f}>{MAX_ALLOCATION}")
            weight = MAX_ALLOCATION
        if weight < MIN_ALLOCATION and weight > 0:
            capped.append(f"{sid}:{weight:.4f}<{MIN_ALLOCATION}→0")
            weight = 0.0

        allocations[sid] = round(weight, 4)
        total += weight

    # ── Total exposure cap ──
    if total > max_total_exposure:
        scale = max_total_exposure / total
        for sid in allocations:
            allocations[sid] = round(allocations[sid] * scale, 4)
        total = max_total_exposure
        capped.append(f"total_exposure_scaled:{scale:.2f}")

    return {
        "allocations": allocations,
        "total_exposure": round(total, 4),
        "capped": capped,
        "meta": {
            "input_count": len(strategies),
            "output_count": len([v for v in allocations.values() if v > 0]),
            "regime_profile": regime_profile,
            "total_capital": total_capital,
        },
    }
