"""AI learning loop — probability calibration from real outcomes.

Compares the probability the AI predicted at signal time against what
actually happened in closed paper trades, bucketed by predicted probability.
Once a bucket has enough samples, future probability estimates are blended
toward the observed win rate — the system literally learns from its record.
"""
from sqlalchemy.orm import Session

from .. import models

BUCKETS: list[tuple[float, float]] = [
    (0, 45), (45, 55), (55, 62), (62, 70), (70, 100),
]
MIN_SAMPLES = 5      # bucket must have at least this many closed trades
BLEND = 0.35         # weight given to empirical win rate


def get_calibration(db: Session) -> dict:
    rows = (
        db.query(models.PaperTrade, models.Signal)
        .join(models.Signal, models.PaperTrade.signal_id == models.Signal.id)
        .filter(models.PaperTrade.status == "closed")
        .all()
    )
    buckets = []
    for lo, hi in BUCKETS:
        sample = [(t, s) for t, s in rows if lo <= s.probability < hi]
        wins = sum(1 for t, _ in sample if t.pnl > 0)
        buckets.append({
            "range": [lo, hi],
            "count": len(sample),
            "predicted_avg": round(sum(s.probability for _, s in sample) / len(sample), 1) if sample else None,
            "actual_win_rate": round(wins / len(sample) * 100, 1) if sample else None,
        })
    return {
        "samples": len(rows),
        "min_samples_per_bucket": MIN_SAMPLES,
        "blend": BLEND,
        "buckets": buckets,
        "active": any(b["count"] >= MIN_SAMPLES for b in buckets),
    }


def adjust_probability(predicted: float, calibration: dict | None) -> float:
    """Blend the model's predicted probability toward the empirical win rate
    of its bucket, once that bucket has enough history."""
    if not calibration:
        return predicted
    for b in calibration.get("buckets", []):
        lo, hi = b["range"]
        if lo <= predicted < hi and b["count"] >= MIN_SAMPLES and b["actual_win_rate"] is not None:
            adjusted = predicted * (1 - BLEND) + b["actual_win_rate"] * BLEND
            return round(max(5.0, min(90.0, adjusted)), 1)
    return predicted
