from sklearn.metrics import roc_auc_score, roc_curve, accuracy_score, recall_score, precision_score
import math

def find_nearest(array, value):
    array = np.asarray(array)
    idx = (np.abs(array - value)).argmin()
    return np.unravel_index(idx, array.shape)

def confusion_matrix(y_pred, y):
    true_pos = np.sum((y_pred == 1) & (y == 1))
    false_pos = np.sum((y_pred == 1) & (y == 0))
    true_neg = np.sum((y_pred == 0) & (y == 0))
    false_neg = np.sum((y_pred == 0) & (y == 1))
    return true_neg, false_neg, false_pos, true_pos

def get_accuracy(y_pred, y):
    return np.sum(1 for i in range(len(y)) if (y_pred[i].all() == y[i].all())) / len(y)

def get_threshold(model, yprob, ytrue, metric_of_interest, desired_metric_value, error, match_number):
    y = ytrue
    probs = yprob
    threshold_metrics = pd.DataFrame(np.zeros((500,8)),index=np.linspace(0,1,500),columns=['Recall','Precision','F1-Score','Accuracy','Specificity','PPV','NPV','AUC'])
    prev = 1/(match_number+1)
    for t in np.linspace(0,1,500):
        pred = np.where(probs>t,1,0)
        true_neg, false_neg, false_pos, true_pos = confusion_matrix(pred, y)
        accuracy = get_accuracy(pred, y)
        recall =  true_pos / (false_neg + true_pos)
        precision = true_pos / (false_pos + true_pos)
        specificity = true_neg/(true_neg+false_pos)
        #for ppv and npv, set prevalance 
        prev = 1/(match_number+1)
        ppv = (recall* (prev))/(recall * prev + (1-specificity) * (1-prev))
        if true_neg== 0 and false_neg==0:
            npv = 0
        else:
            npv = (specificity* (1-prev))/(specificity * (1-prev) + (1-recall) * (prev))
        f1score = 2*(precision*recall)/(precision+recall)
        roc_auc = roc_auc_score(y, pred)
        threshold_metrics.loc[t,['Recall','Precision','F1-Score','Accuracy','Specificity','PPV','NPV','AUC']] = [recall,precision,f1score,accuracy,specificity,ppv,npv,roc_auc]
    
    ### Find results for best threshold
    condition1 = threshold_metrics.loc[:,metric_of_interest] < desired_metric_value + error
    condition2 = threshold_metrics.loc[:,metric_of_interest] > desired_metric_value - error
    combined_condition = condition1 & condition2
    if metric_of_interest == 'Recall':
        sort_col = 'Precision'
    elif metric_of_interest == 'Precision':
        sort_col = 'Recall'
    elif metric_of_interest == 'F1-Score':
        sort_col = 'F1-Score'
    sorted_results = threshold_metrics[combined_condition].sort_values(by=sort_col,ascending=False)
    print(sorted_results)
    if len(sorted_results) > 0:
        """ Only Record Value if Condition is Satisfied """
        results_df.loc[['Recall','Precision','F1-Score','Accuracy','Specificity','PPV','NPV','AUC'],1] = sorted_results.iloc[0,:]   
        best_threshold = sorted_results.iloc[0,:].name
    else:
        print('No Threshold Found for Constraint!')
    return best_threshold, results_df

def get_test_results_from_threshold(yhat_test, y_test, thresh,n1,n2):
    y = y_test
    unique, frequency = np.unique(y_test,return_counts = True)
    pred = np.where(yhat_test>thresh,1,0)
    true_neg, false_neg, false_pos, true_pos = confusion_matrix(pred, y)
    accuracy_test = get_accuracy(pred, y)
    #recall
    recall_test =  true_pos / (false_neg + true_pos)
    #precision
    precision_test = true_pos / (false_pos + true_pos)
    #specificity
    specificity_test = true_neg/(true_neg+false_pos)
    #for ppv and npv, set prevalance 
    #prev = 1/(match_number+1)
    prev = frequency[1]/len(y_test)
    #ppv
    ppv_test = (recall_test* (prev))/(recall_test * prev + (1-specificity_test) * (1-prev))
    if true_neg== 0 and false_neg==0:
        npv_test = 0
    else:
        npv_test = (specificity_test* (1-prev))/(specificity_test * (1-prev) + (1-recall_test) * (prev))
    f1score_test = 2*(precision_test*recall_test)/(precision_test+recall_test)
    roc_auc_test = roc_auc_score(y, yhat_test)
    auroc = roc_auc_test

    print('Test results')
    print('Accuracy: {:.4f}'.format(accuracy_test))
    print('Recall: {:.4f}'.format(recall_test))
    print('Precision: {:.4f}'.format(precision_test))
    print('Specificity: {:.4f}'.format(specificity_test))
    print('F1 Score: {:.4f}'.format(f1score_test))
    print('PPV: {:.4f}'.format(ppv_test))
    print('NPV: {:.4f}'.format(npv_test))
    print('AUC: {:.4f}'.format(roc_auc_test))
    return accuracy_test, recall_test, precision_test, specificity_test, f1score_test, ppv_test, npv_test, roc_auc_test