from __future__ import annotations

import math

import numpy as np
from sklearn.calibration import calibration_curve
from sklearn.metrics import (
    balanced_accuracy_score,
    brier_score_loss,
    confusion_matrix,
    f1_score,
    precision_score,
    recall_score,
)


def classification_metrics(y_true: np.ndarray, y_pred: np.ndarray, probabilities: np.ndarray) -> dict[str, object]:
    classes = np.array([-1, 0, 1])
    result: dict[str, object] = {
        "balanced_accuracy": float(balanced_accuracy_score(y_true, y_pred)),
        "macro_precision": float(precision_score(y_true, y_pred, average="macro", zero_division=0)),
        "macro_recall": float(recall_score(y_true, y_pred, average="macro", zero_division=0)),
        "macro_f1": float(f1_score(y_true, y_pred, average="macro", zero_division=0)),
        "buy_precision": float(precision_score(y_true == 1, y_pred == 1, zero_division=0)),
        "sell_precision": float(precision_score(y_true == -1, y_pred == -1, zero_division=0)),
        "confusion_matrix": confusion_matrix(y_true, y_pred, labels=classes).tolist(),
        "class_order": classes.tolist(),
    }
    brier: dict[str, float] = {}
    calibration: dict[str, list[float]] = {}
    for index, label in enumerate(classes):
        actual = (y_true == label).astype(int)
        brier[str(label)] = float(brier_score_loss(actual, probabilities[:, index]))
        if len(np.unique(actual)) > 1:
            observed, predicted = calibration_curve(actual, probabilities[:, index], n_bins=10, strategy="quantile")
            calibration[str(label)] = [
                {"predicted": float(p), "observed": float(o)} for p, o in zip(predicted, observed)
            ]
    result["brier_by_class"] = brier
    result["mean_brier"] = float(np.mean(list(brier.values()))) if brier else math.nan
    result["calibration"] = calibration
    return result

