"""llm_calibrator.py — LLM confidence calibration via histogram binning.

Uses validated agent_judgments records to build accuracy bins. Each bin maps
a raw-confidence range to the observed accuracy within that range. Bins with
fewer than MIN_SAMPLES_PER_BIN records fall back to raw confidence.

Replaces the simple factor-based +-0.03 adjustment in confidence_calibrator.py
for LLM-originated confidence values.

Usage::

    from src.learning.llm_calibrator import calibrate_llm_confidence
    result = calibrate_llm_confidence(0.82)
    # {"calibrated": 0.75, "bin_n": 45, "bin_accuracy": 0.75, "used_fallback": False}
"""

from __future__ import annotations

from typing import Any

_BIN_WIDTH = 0.05
_MIN_SAMPLES_PER_BIN = 10
_MIN_TOTAL_SAMPLES = 50
_BIN_EDGES = [round(i * _BIN_WIDTH, 2) for i in range(0, 21)]  # 0.00 .. 1.00


def _build_bins() -> dict[int, dict[str, Any]]:
    """Query agent_judgments and build accuracy bins from validated records."""
    try:
        from src.learning.trading_brain import get_brain

        brain = get_brain()
        conn = brain._conn
        rows = conn.execute(
            """
            SELECT confidence, direction_correct
            FROM agent_judgments
            WHERE validated_at != '' AND direction_correct >= 0 AND confidence > 0
            """
        ).fetchall()
        if not rows or len(rows) < _MIN_TOTAL_SAMPLES:
            return {}

        bins: dict[int, dict[str, Any]] = {}
        for i in range(20):
            bins[i] = {"low": round(i * _BIN_WIDTH, 2),
                       "high": round((i + 1) * _BIN_WIDTH, 2),
                       "n": 0, "correct": 0}

        for row in rows:
            conf = float(row["confidence"] or 0.5)
            correct = int(row["direction_correct"] or 0)
            idx = min(int(conf / _BIN_WIDTH), 19)
            bins[idx]["n"] += 1
            bins[idx]["correct"] += correct

        return bins
    except Exception:
        return {}


def calibrate_llm_confidence(raw_confidence: float) -> dict[str, Any]:
    """Calibrate a raw LLM confidence value using historical accuracy bins.

    Args:
        raw_confidence: 0.0–1.0, the raw confidence from LLM.

    Returns:
        {"calibrated": float, "bin_n": int, "bin_accuracy": float | None,
         "used_fallback": bool}
    """
    conf = max(0.0, min(1.0, float(raw_confidence or 0.5)))
    bins = _build_bins()

    if not bins:
        return {
            "calibrated": round(conf, 4),
            "bin_n": 0,
            "bin_accuracy": None,
            "used_fallback": True,
            "fallback_reason": "insufficient_data",
        }

    idx = min(int(conf / _BIN_WIDTH), 19)
    b = bins.get(idx, {})
    n = int(b.get("n", 0) or 0)

    if n < _MIN_SAMPLES_PER_BIN:
        for offset in range(1, 3):
            for neighbor in (idx - offset, idx + offset):
                nb = bins.get(neighbor, {})
                if int(nb.get("n", 0) or 0) >= _MIN_SAMPLES_PER_BIN:
                    b = nb
                    n = int(b["n"])
                    break
            if n >= _MIN_SAMPLES_PER_BIN:
                break

    if n < _MIN_SAMPLES_PER_BIN:
        return {
            "calibrated": round(conf, 4),
            "bin_n": n,
            "bin_accuracy": None,
            "used_fallback": True,
            "fallback_reason": f"bin_{idx}_insufficient_samples",
        }

    bin_accuracy = round(int(b["correct"]) / n, 4)
    calibrated = round(bin_accuracy, 4)

    return {
        "calibrated": calibrated,
        "bin_n": n,
        "bin_accuracy": bin_accuracy,
        "used_fallback": False,
    }
