"""Statistical Probability Calibration & Reliability Metrics."""
from __future__ import annotations

import math
from typing import Any, Sequence


def calculate_brier_score(y_true: Sequence[int], y_prob: Sequence[float]) -> float:
    """Calculate mean squared error between predicted probabilities and binary outcomes."""
    n = len(y_true)
    if n == 0:
        return 0.0
    return sum((prob - true) ** 2 for true, prob in zip(y_true, y_prob)) / n


def compute_reliability_curve(
    y_true: Sequence[int], y_prob: Sequence[float], n_bins: int = 5
) -> list[dict[str, float]]:
    """Compute calibration curve data points for reliability diagnostics."""
    bins = [[] for _ in range(n_bins)]
    bin_size = 1.0 / n_bins

    for true, prob in zip(y_true, y_prob):
        bin_idx = min(int(prob / bin_size), n_bins - 1)
        bins[bin_idx].append((true, prob))

    curve: list[dict[str, float]] = []
    for i, b in enumerate(bins):
        bin_center = (i + 0.5) * bin_size
        if not b:
            curve.append({"bin_center": round(bin_center, 2), "mean_pred": round(bin_center, 2), "true_prob": 0.0, "count": 0})
            continue

        mean_pred = sum(p for _, p in b) / len(b)
        true_prob = sum(t for t, _ in b) / len(b)
        curve.append({
            "bin_center": round(bin_center, 2),
            "mean_pred": round(mean_pred, 3),
            "true_prob": round(true_prob, 3),
            "count": len(b),
        })

    return curve


class PlattCalibrator:
    """Calibrates raw margin scores into out-of-sample posterior probabilities via sigmoid scaling."""

    def __init__(self) -> None:
        self.a: float = 1.0
        self.b: float = 0.0

    def fit(self, scores: Sequence[float], y_true: Sequence[int]) -> None:
        """Estimate logistic calibration parameters via simple gradient descent."""
        lr = 0.01
        for _ in range(100):
            grad_a = 0.0
            grad_b = 0.0
            n = len(scores)
            if n == 0:
                break
            for s, y in zip(scores, y_true):
                z = self.a * s + self.b
                p = 1.0 / (1.0 + math.exp(-max(min(z, 20.0), -20.0)))
                err = p - y
                grad_a += err * s
                grad_b += err
            self.a -= lr * (grad_a / n)
            self.b -= lr * (grad_b / n)

    def calibrate(self, raw_score: float) -> float:
        z = self.a * raw_score + self.b
        return 1.0 / (1.0 + math.exp(-max(min(z, 20.0), -20.0)))


platt_calibrator = PlattCalibrator()
