from __future__ import annotations

from typing import Any


def _clamp01(value: float) -> float:
    return max(0.0, min(1.0, float(value)))


def _iter_samples(recent_closures: list[dict[str, Any]]) -> list[tuple[float, int]]:
    samples: list[tuple[float, int]] = []
    for row in recent_closures:
        if not isinstance(row, dict):
            continue
        conf_raw = row.get("entry_confidence", row.get("confidence", None))
        pnl_raw = row.get("pnl", None)
        try:
            conf = float(conf_raw)
            pnl = float(pnl_raw)
        except Exception:
            continue
        if conf <= 0.0 or conf > 1.0:
            continue
        samples.append((_clamp01(conf), 1 if pnl > 0 else 0))
    return samples


def calibrate_confidence_online(
    raw_confidence: float,
    recent_closures: list[dict[str, Any]] | None,
    *,
    bin_count: int = 8,
) -> tuple[float, dict[str, Any]]:
    """
    Online confidence calibration based on recent closed-trade outcomes.
    - Uses bin-level win rate with beta prior.
    - Blends bin estimate with global estimate.
    - Applies bounded adjustment to avoid unstable jumps.
    """
    raw = _clamp01(raw_confidence)
    rows = recent_closures if isinstance(recent_closures, list) else []
    samples = _iter_samples(rows[-80:])
    total_n = len(samples)
    if total_n < 8:
        return raw, {
            "enabled": True,
            "ready": False,
            "raw": round(raw, 6),
            "calibrated": round(raw, 6),
            "sample_count": total_n,
            "reason": "insufficient_samples",
        }

    # Global win rate with weak prior.
    wins = sum(y for _, y in samples)
    global_rate = (wins + 2.0) / (total_n + 4.0)

    # Bin stats.
    bins = [{"n": 0, "w": 0} for _ in range(bin_count)]
    for conf, y in samples:
        idx = min(bin_count - 1, int(conf * bin_count))
        bins[idx]["n"] += 1
        bins[idx]["w"] += y
    raw_idx = min(bin_count - 1, int(raw * bin_count))
    b = bins[raw_idx]
    bin_n = int(b["n"])
    bin_rate = ((b["w"] + 1.0) / (bin_n + 2.0)) if bin_n > 0 else global_rate

    # Local estimate blends bin and global by local sample size.
    local_weight = min(0.8, bin_n / 20.0)
    local_estimate = local_weight * bin_rate + (1.0 - local_weight) * global_rate

    # Adjustment strength grows with total samples but stays bounded.
    adjust_strength = min(0.45, total_n / 60.0 * 0.45)
    calibrated = raw * (1.0 - adjust_strength) + local_estimate * adjust_strength

    # Bounded shift for stability.
    max_shift = 0.15
    calibrated = max(raw - max_shift, min(raw + max_shift, calibrated))
    calibrated = _clamp01(calibrated)

    return calibrated, {
        "enabled": True,
        "ready": True,
        "raw": round(raw, 6),
        "calibrated": round(calibrated, 6),
        "delta": round(calibrated - raw, 6),
        "sample_count": total_n,
        "global_win_rate": round(global_rate, 6),
        "bin_index": raw_idx,
        "bin_samples": bin_n,
        "bin_win_rate": round(bin_rate, 6),
        "adjust_strength": round(adjust_strength, 6),
    }
