import pandas as pd
import numpy as np

def score_numeric_variable(score_card_variable, applications, variable):
    bin_scores = score_card_variable
    bin_scores['bin'] = bin_scores['bin'].astype('float')
    bin_scores[variable + '_score'] = bin_scores['score']
    bin_scores[variable + '_label'] = bin_scores['label']
    bin_scores = bin_scores.sort_values(by=['bin'])
    bin_scores[variable + '_index'] = range(len(bin_scores))
    if bin_scores['sign'].iloc[0] == True:
        applications[variable + '_index'] = pd.cut(applications[variable], [-np.inf] + list(bin_scores['bin'])[1:] + [np.inf], labels=range(len(bin_scores)))
    else:
        applications[variable + '_index'] = pd.cut(applications[variable], [-np.inf] + list(bin_scores['bin'])[:-1] + [np.inf], labels=range(len(bin_scores)))
    if len(bin_scores[bin_scores['include_na']]) > 0:
        applications[variable + '_index'] = applications[variable + '_index'].fillna(bin_scores[bin_scores['include_na']][variable + '_index'].iloc[0])
    applications = pd.merge(applications, bin_scores[[variable + '_index', variable + '_score', variable + '_label']], on=variable + '_index', how='left')
    return applications

def score_categorical_variable(score_card_variable, applications, variable):
    bin_scores = score_card_variable[['category', 'score', 'label']]
    bin_scores.columns = [variable, variable + '_score', variable + '_label']
    applications[variable] = applications[variable].astype(str)
    bin_scores[variable] = bin_scores[variable].astype(str)
    applications = pd.merge(applications, bin_scores, on=variable, how='left')
    if len(bin_scores[pd.isna(bin_scores[variable])]) > 0:
        applications[variable + '_score'][applications[variable]=="nan"] = bin_scores[pd.isna(bin_scores[variable])][variable + '_score'].iloc[0]
        applications[variable + '_label'][applications[variable]=="nan"] = bin_scores[pd.isna(bin_scores[variable])][variable + '_label'].iloc[0]
    if len(applications[pd.isna(applications[variable + '_score'])]) > 0:
        # handle never seen values with conservative score
        worst_category = bin_scores[bin_scores[variable + '_score'] == min(bin_scores[variable + '_score'])][[variable + '_score', variable + '_label']].iloc[0]
        applications[variable + '_score'][pd.isna(applications[variable + '_score'])] = worst_category[variable + '_score']
        applications[variable + '_label'][pd.isna(applications[variable + '_label'])] = worst_category[variable + '_label']
        applications[variable + '_score'] = applications[variable + '_score'].astype(np.int64)
    return applications
    
def score_variable(score_card_variable, applications, variable):
    if score_card_variable['category'].isnull().all():
        applications = score_numeric_variable(score_card_variable, applications, variable)
    else:
        applications = score_categorical_variable(score_card_variable, applications, variable)
    return applications

def score(score_card, applications):
    variables = list(score_card['variable'].unique())
    applications = applications[['credit_event', 'id'] + variables]
    
    for variable in variables:
        score_card_variable = score_card[score_card['variable']==variable]
        applications = score_variable(score_card_variable, applications, variable)
    return applications

def score_and_compute_perf(score_card, credit_performance, applications):
    applications = score(score_card, applications)
    
    variables = score_card['variable'].unique()
    variable_scores = variables + '_score'
    
    scores = list(applications[variable_scores].sum(axis=1))
    labels = {variable: applications[variable + '_label'].iloc[0] for variable in variables}
    credit_perf = list(np.interp(scores, credit_performance['score_avg'], credit_performance['credit_event_avg']))
    
    return {'score': scores, 'credit_perf': credit_perf, 'labels': labels}
    