# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
# See the LICENSE file in the root of this repository for details.
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial

import plotly.graph_objects as go
from matplotlib import cm

from validmind import tags, tasks
from validmind.vm_models import VMDataset


@tags("visualization", "credit_risk", "logistic_regression")
@tasks("classification")
def ScorecardHistogram(
    dataset: VMDataset,
    title: str = "Histogram of Scores",
    score_column: str = "score",
) -> go.Figure:
    """
    The Scorecard Histogram test evaluates the distribution of credit scores between default and non-default instances,
    providing critical insights into the performance and generalizability of credit-risk models.

    ### Purpose

    The Scorecard Histogram test metric provides a visual interpretation of the credit scores generated by a machine
    learning model for credit-risk classification tasks. It aims to compare the alignment of the model's scoring
    decisions with the actual outcomes of credit loan applications. It helps in identifying potential discrepancies
    between the model's predictions and real-world risk levels.

    ### Test Mechanism

    This metric uses logistic regression to generate a histogram of credit scores for both default (negative class) and
    non-default (positive class) instances. Using both training and test datasets, the metric calculates the credit
    score of each instance with a scorecard method, considering the impact of different features on the likelihood of
    default. It includes the default point to odds (PDO) scaling factor and predefined target score and odds settings.
    Histograms for training and test sets are computed and plotted separately to offer insights into the model's
    generalizability to unseen data.

    ### Signs of High Risk

    - Discrepancies between the distributions of training and testing data, indicating a model's poor generalization
    ability
    - Skewed distributions favoring specific scores or classes, representing potential bias

    ### Strengths

    - Provides a visual interpretation of the model's credit scoring system, enhancing comprehension of model behavior
    - Enables a direct comparison between actual and predicted scores for both training and testing data
    - Its intuitive visualization helps understand the model's ability to differentiate between positive and negative
    classes
    - Can unveil patterns or anomalies not easily discerned through numerical metrics alone

    ### Limitations

    - Despite its value for visual interpretation, it doesn't quantify the performance of the model and therefore may
    lack precision for thorough model evaluation
    - The quality of input data can strongly influence the metric, as bias or noise in the data will affect both the
    score calculation and resultant histogram
    - Its specificity to credit scoring models limits its applicability across a wider variety of machine learning
    tasks and models
    - The metric's effectiveness is somewhat tied to the subjective interpretation of the analyst, relying on their
    judgment of the characteristics and implications of the plot.
    """

    if score_column not in dataset.df.columns:
        raise ValueError(
            f"The required column '{score_column}' is not present in the dataset with input_id {dataset.input_id}"
        )

    df = dataset.df

    fig = _plot_score_histogram(df, score_column, dataset.target_column, title)

    return fig


def _plot_score_histogram(df, score_col, target_col, title):
    # Generate a colormap and convert to Plotly-accepted color format
    # Adjust 'viridis' to any other matplotlib colormap if desired
    colormap = cm.get_cmap("viridis")

    fig = go.Figure()

    # Get unique classes and assign colors
    classes = sorted(df[target_col].unique())
    colors = [colormap(i / len(classes))[:3] for i in range(len(classes))]  # RGB
    color_dict = {
        cls: f"rgb({int(rgb[0]*255)}, {int(rgb[1]*255)}, {int(rgb[2]*255)})"
        for cls, rgb in zip(classes, colors)
    }

    for class_value in sorted(df[target_col].unique()):
        scores_class = df[df[target_col] == class_value][score_col]
        fig.add_trace(
            go.Histogram(
                x=scores_class,
                opacity=0.75,
                name=f"{target_col} = {class_value}",
                marker=dict(
                    color=color_dict[class_value],
                ),
            )
        )
        fig.update_layout(
            barmode="overlay",
            title_text=f"{title}",
            xaxis_title="Score",
            yaxis_title="Frequency",
        )
    return fig
