"""
Financial evaluation metrics for TCGM.
Provides:
    - AUC
    - Brier Score
    - Expected Financial Loss
    - Expected Monetary Loss (threshold optimizer)
"""

import numpy as np
from sklearn.metrics import roc_auc_score, brier_score_loss
from .loss import financial_loss


# -------------------------------------------------------------------------
# AUC calculation (safe when only one class exists)
# -------------------------------------------------------------------------
def _safe_auc(y_true, y_prob):
    y_true = np.asarray(y_true)
    y_prob = np.asarray(y_prob)

    # AUC is undefined if only one class exists
    if len(np.unique(y_true)) < 2:
        return float("nan")

    return float(roc_auc_score(y_true, y_prob))


# -------------------------------------------------------------------------
# Brier Score
# -------------------------------------------------------------------------
def _brier(y_true, y_prob):
    return float(brier_score_loss(y_true, y_prob))


# -------------------------------------------------------------------------
# Financial Expected Loss (model-implied asymmetric loss)
# -------------------------------------------------------------------------
def evaluate_financial_performance(y_true, y_prob, cost_fp=1.0, cost_fn=5.0):
    """
    Computes:
        - AUC
        - Brier Score
        - Expected Loss using the financial asymmetric loss function
    
    y_true: array-like of 0/1 labels
    y_prob: predicted probabilities
    """
    y_true = np.asarray(y_true).astype(int)
    y_prob = np.clip(np.asarray(y_prob), 0.0, 1.0)

    auc = _safe_auc(y_true, y_prob)
    brier = _brier(y_true, y_prob)

    # Mean asymmetric loss across all samples
    exp_loss = float(np.mean(financial_loss(y_true, y_prob, cost_fp, cost_fn)))

    return {
        "AUC": auc,
        "Brier": brier,
        "Expected_Loss": exp_loss,
    }


# -------------------------------------------------------------------------
# Expected Monetary Loss (threshold sweep for decision optimization)
# -------------------------------------------------------------------------
def compute_expected_monetary_loss(
    y_true,
    y_prob,
    exposure,
    lgd=0.6,
    cost_fp=50.0,
    thresholds=None
):
    """
    Computes threshold-dependent monetary loss:
        FP → operational cost
        FN → exposure × LGD

    Returns:
        {
            "best_threshold": float,
            "best_loss": float,
            "thresholds": array,
            "loss_curve": array
        }

    y_true: binary ground truth (0/1)
    y_prob: model predicted probabilities
    exposure: monetary exposure per transaction
    lgd: loss given default (fraction of exposure lost)
    cost_fp: cost of wrongly blocking a legitimate transaction
    thresholds: optional iterable of thresholds
    """

    y_true = np.asarray(y_true).astype(int)
    y_prob = np.clip(np.asarray(y_prob), 0.0, 1.0)
    exposure = np.asarray(exposure).astype(float)

    if thresholds is None:
        thresholds = np.linspace(0.01, 0.99, 99)

    loss_values = []

    best_loss = np.inf
    best_threshold = None

    for t in thresholds:
        decisions = (y_prob >= t).astype(int)

        # False Positives → blocking legitimate customers
        fp_mask = (decisions == 1) & (y_true == 0)
        fp_loss = fp_mask.sum() * cost_fp

        # False Negatives → missed fraud → lose exposure × LGD
        fn_mask = (decisions == 0) & (y_true == 1)
        fn_loss = np.sum(fn_mask * exposure * lgd)

        total_loss = fp_loss + fn_loss
        loss_values.append(total_loss)

        if total_loss < best_loss:
            best_loss = total_loss
            best_threshold = t

    return {
        "best_threshold": float(best_threshold),
        "best_loss": float(best_loss),
        "thresholds": np.array(thresholds),
        "loss_curve": np.array(loss_values)
    }