# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_lightning_prediction.ipynb.

# %% auto 0
__all__ = ['LnPreds']

# %% ../nbs/00_lightning_prediction.ipynb 3
import pandas as pd
import os
import time
import random as random
from collections import Counter
from datetime import datetime
from pathlib import Path
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
import seaborn as sns
import matplotlib.dates as mdates
import datetime
from datetime import datetime

# %% ../nbs/00_lightning_prediction.ipynb 4
class LnPreds:
    def __init__(self,path):
        self.yrs_df = None
        self.yr_fmt_df= None
        self.wks_df = None
        self.wk_fmt_df = None
        self.wk_stack_fmt_df=None
        self.df = pd.read_csv(path);
        self.prep_data()
        self.cln_data()
        self.yrs_trnd()
        self.wks_trnd()
        
    def prep_data(self):
        "Parse the datetime and calculates values of bitcoin for each transaction"
        self.df['DATE'] = pd.to_datetime(self.df['PAYMENT_DATE'],format="%d-%b-%Y %H:%M:%S")
        self.df.drop('PAYMENT_DATE', inplace=True, axis=1)
        self.df = self.df.loc[:,['DATE','PAYMENT_TYPE','MERCHANT_ID','AMOUNT_BTC','AMOUNT_USD']]
        self.df = self.df.sort_values('DATE').reset_index(drop=True)
        self.df['BTC_USD'] = self.df['AMOUNT_USD'] / self.df['AMOUNT_BTC']
        
    def cln_data(self):
        "Removes inconsistent records from the data"
        self.df.query("BTC_USD<1000").sort_values('BTC_USD')
        mask = self.df['BTC_USD'] < 1000
        self.df['BTC_USD'] = np.where(mask, np.nan, self.df['BTC_USD'])
        self.df['BTC_USD'] = self.df['BTC_USD'].fillna(method='ffill')
        self.df['AMOUNT_BTC'] = np.where(mask,
                                    self.df['AMOUNT_USD'] / self.df['BTC_USD'].shift(1),
                                    self.df['AMOUNT_BTC'])
        self.df = self.df.loc[~(self.df['AMOUNT_USD']>=0) != (self.df['PAYMENT_TYPE']=='In')]
        self.df.drop(columns=['BTC_USD'], inplace=True)
    
    def yrs_trnd(self):
        "Prepares a dataframe for month-by-month yearly analysis"
        yrs_df=self.df.copy()
        yrs_df['YEAR'] = yrs_df['DATE'].dt.year.astype(str)
        yrs_df['MONTH'] = yrs_df['DATE'].dt.month_name().str.slice(stop=3)
        yrs_df['MONTH'] = pd.Categorical(yrs_df['MONTH'], 
                                     ordered=True, 
                                     categories=['Jan','Feb','Mar',
                                                 'Apr','May','Jun','Jul',
                                                 'Aug','Sep','Oct',
                                                 'Nov','Dec'])
        self.yr_fmt_df=yrs_df
        yrs_df=yrs_df.groupby(["MERCHANT_ID","YEAR","MONTH"])['AMOUNT_BTC'].sum().to_frame()
        self.yrs_df=yrs_df
        
    def wks_trnd(self):
        "Format the data into the days of the week"
        cp_df=self.yr_fmt_df.copy()
        cp_df['DAY']=cp_df['DATE'].dt.day_name().str.slice(stop=3)
        days = ['Mon','Tue','Wed','Thu','Fri','Sat', 'Sun']
        self.wk_fmt_df=cp_df
        wk_df=cp_df.groupby(['MERCHANT_ID','YEAR','MONTH','DAY'])\
        ['AMOUNT_BTC'].sum().to_frame().reindex(days,level=3)
        self.wks_df=wk_df 
    
    def wks_stack(self,merchant_id,#ID of the merchant of interest
                  yr):#Year of interest
        "Formats the week-formatted-data to accomodate selecting `merchant_id` and `yr`"
        days = ['Mon','Tue','Wed','Thu','Fri','Sat', 'Sun']
        stacked_data = self.wk_fmt_df.groupby(['MERCHANT_ID','YEAR','PAYMENT_TYPE','MONTH','DAY'])['AMOUNT_BTC'].sum().to_frame()
        stacked_plt_data=stacked_data.loc[(merchant_id,yr)]
        stacked_plt_data=stacked_plt_data.reindex(days,level=2)
        self.wk_stack_fmt_df=stacked_plt_data
        return stacked_plt_data
    
    def get_weekends(self,day_df,#Dataframe in days
                     int_yr,#Year of interest
                     int_mnth): #Month of interest
        "Provides weekends for the requested months based on the year requested"
        su_cont=[]
        for yr in int_yr:
            in_cont=[]
            for mnth in int_mnth: 
                jan_data=day_df[(day_df['MONTH']==mnth)&(day_df['YEAR']==yr)]
                weekends=set(np.where(jan_data['WEEKEND']>4,jan_data['DAY'],'not weekend'))
                weekends.remove('not weekend')
                weekends=sorted([int(x) for x in list(weekends)])
                in_cont.append(weekends)
            su_cont.append(in_cont)
        return su_cont
    
    
    def dy_mnth(self,strt_yr,#Starting year
                numb_yrs,#Number of years
                strt_mnth,#Start of the month
                numb_mnths):# Number of months
        "Format the data in daily distribution of transactions for a specified period of time"
        day_df=self.yr_fmt_df 
        day_df['DAY']=day_df['DATE'].dt.day
        day_df['WEEKEND']=day_df['DATE'].dt.dayofweek
        all_yrs=sorted(list(set(day_df['YEAR'])))
        all_mnths=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
        int_yr=all_yrs[strt_yr:numb_yrs]
        int_mnth=all_mnths[strt_mnth:numb_mnths]
        su_cont=self.get_weekends(day_df,int_yr,int_mnth)
        grp_df=day_df.groupby(["YEAR","MONTH","DAY"])['AMOUNT_BTC'].sum().to_frame()
        return grp_df,su_cont,int_yr,int_mnth

    
    def imp_merch_mnth(self,merch_id,#Merchant ID
                       yr,#Selected Year
                       num_mnths,#Number of months
                       cols):#Number of columns
        "Selects `merch_id` and `yr` based on the months with high amount of transactions"
        plt_data=self.wks_df.loc[(merch_id,yr)]
        months=plt_data.index.get_level_values(0).unique().to_list()
        month_cont={}
        for idx,mnth in enumerate(months):
            month_cont[months[idx]]=plt_data.loc[mnth].values.sum()
        cont={k: v for k, v in sorted(month_cont.items(), key=lambda item: item[1],reverse=True)}
        nw_month=list(cont.keys())[:num_mnths]
        batched_months = [nw_month[x:x+cols] for x in range(0,len(nw_month),cols)]
        return batched_months,plt_data
    
    def plt_yrs_trnd(self,num_merch,#Number of merchants
                     cols):#Number of columns
        "Plots a graph of the yearly trend across months using `num_merch` and `cols`"
        imp_merch=list(self.yrs_df.groupby('MERCHANT_ID').sum()\
                       .sort_values(by='AMOUNT_BTC', ascending=False).iloc[:num_merch].index)
        imp_merch=[imp_merch[x:x+cols] for x in range(0,len(imp_merch),cols)]
        years=self.yrs_df.index.get_level_values(1).unique().to_list()
        rows,colms=np.array(imp_merch).shape
        sns.set_theme(style='darkgrid')
        fig, ax = plt.subplots(rows,colms,figsize=(18,13),sharey=True)
        for idx1, merchants in enumerate(imp_merch):
            for idx2,merchant in enumerate(merchants):    
                for year in years:
                    merch=self.yrs_df.loc[(merchant,year)]
                    ax[idx1,idx2].plot_date(merch.index.to_list(),\
                                            merch['AMOUNT_BTC'].values, label=year, linestyle="-")
                    if idx2==0:
                        ax[idx1,idx2].set_ylabel("AMOUNT IN BTC")
                    ax[idx1,idx2].set_title(f'Merchant {merchant}',fontsize=15)
                    ax[idx1,idx2].legend()
        fig.suptitle('YEARLY BALANCE TRENDLINES OVER JANUARY TO DECEMBER',fontsize=20)
        fig.align_labels()
        fig.tight_layout(); 
    
    def plt_wks_trnd(self,merch_id,#Merchant ID
                     yr,#Selected year
                     num_mnths,#Number of months
                     cols):#Number of columns
        batched_months,plt_data=self.imp_merch_mnth(merch_id,yr,num_mnths,cols)  
        rows,colms=np.array(batched_months).shape
        sns.set_theme(style='darkgrid')
        fig, ax = plt.subplots(rows,colms,figsize=(18,13),sharey=True)
        for idx1,mnths in enumerate(batched_months):
            for idx2,mnth in enumerate(mnths):
                mnth_df=plt_data.loc[mnth]
                bar_colors = ['tab:red', 'tab:blue', 'tab:purple', 'tab:olive','tab:cyan', 'tab:pink', 'tab:gray']
                rects=ax[idx1,idx2].bar(mnth_df.index, np.round(mnth_df['AMOUNT_BTC'].values), color=bar_colors)
                if idx2==0:   
                    ax[idx1,idx2].set_ylabel("AMOUNT IN BTC")
                ax[idx1,idx2].set_title(f'{mnth}',fontsize=15)
                ax[idx1,idx2].bar_label(rects,padding=3)
        fig.suptitle(f'WEEKLY BALANCE PAYMENTS FOR MERCHANT {merch_id} IN {yr}',fontsize=20)
        fig.align_labels()
        fig.tight_layout();
        
    def plt_wks_stack(self,merch_id,#Merchant ID
                      yr,#Year selected
                      num_mnths,#Number of months
                      cols):#Number of columns
        "Plots a group bar chart of both inputs and outputs in the weekdays"
        batched_months,_=self.imp_merch_mnth(merch_id,yr,num_mnths,cols)
        stacked_plt_data=self.wks_stack(merch_id,yr)
        rows,colms=np.array(batched_months).shape
        sns.set_theme(style='darkgrid')
        fig, ax = plt.subplots(rows,colms,figsize=(18,13),sharey=True)
        for idx1,mnths in enumerate(batched_months):
            for idx2,mnth in enumerate(mnths):
                jan_plot_in = stacked_plt_data.loc[('In',mnth)]
                jan_plot_out = stacked_plt_data.loc[('Out',mnth)]
                x_vals=jan_plot_in.index.to_list()
                y_in=jan_plot_in['AMOUNT_BTC'].values
                y_out=jan_plot_out['AMOUNT_BTC'].values                
                labels = x_vals
                inp_vals = np.round(y_in)
                out_vals = np.round(abs(y_out))
                label_loc = np.arange(len(labels))  # the label locations
                width = 0.35  # the width of the bars
                rects1 = ax[idx1,idx2].bar(label_loc - width/2, inp_vals, width, label='Input')
                rects2 = ax[idx1,idx2].bar(label_loc + width/2, out_vals, width, label='Output')
                # Add some text for labels, title and custom x-axis tick labels, etc.
                if idx2==0:  
                    ax[idx1,idx2].set_ylabel('AMOUNT IN BTC')
                ax[idx1,idx2].set_xticks(label_loc, labels)
                ax[idx1,idx2].set_title(f'{mnth}',fontsize=15)
                ax[idx1,idx2].legend()
                ax[idx1,idx2].bar_label(rects1, padding=3)
                ax[idx1,idx2].bar_label(rects2, padding=3)
        fig.suptitle(f'{yr} INPUT AND OUTPUT COMPARISON',fontsize=20)
        fig.align_labels()
        fig.tight_layout();
        
    def plt_dy_mnth(self,strt_yr,#Start year
                    numb_yrs,#Number of years
                    strt_mnth,#Start of the month
                    numb_mnths):#Number of months
        "Plots a daily chart between Selected months"
        grp_df,su_cont,int_yr,int_mnth=self.dy_mnth(strt_yr,numb_yrs,strt_mnth,numb_mnths)
        sns.set_theme(style='darkgrid')
        rows=len(int_yr)
        cols=len(int_mnth)
        fig, ax = plt.subplots(rows,cols,figsize=(20,13))
        for idx1,yr in enumerate(int_yr):
            for idx2,mnth in enumerate(int_mnth):
                slct_yr=grp_df.loc[(yr,mnth)]
                y_data=slct_yr['AMOUNT_BTC'].values
                x_data=slct_yr.index.get_level_values(0).unique().to_list()
                color_tab=[]
                
                weekends=su_cont[idx1][idx2]
                for x in x_data:
                    if x in weekends:
                        color_tab.append('r')
                    else:
                        color_tab.append('b')

                test_info=color_tab
                ttest_inf=test_info.copy()
                seen_cont=[]
                first_item=None
                for idx,x in enumerate(ttest_inf):
                    if idx==0:
                        first_item=x
                        seen_cont.append(x)
                        continue
                    if ttest_inf[idx]==first_item and first_item=='r':
                        ttest_inf[idx]='_r'
                    elif ttest_inf[idx]==first_item and first_item=='b':
                        ttest_inf[idx]='_b'
                    elif x not in seen_cont:
                        first_item=x
                        seen_cont.append(x)
                        continue
                    elif x in seen_cont:
                        if x=='r':
                            ttest_inf[idx]='_r'
                        elif x=='b':
                            ttest_inf[idx]='_b'       
                wk_lg=list()           
                for x in ttest_inf:
                    if x=='r':
                        wk_lg.append('Weekend')
                    elif x=='b':
                        wk_lg.append('Weekday')
                    else:
                        wk_lg.append('_wkdy')
                for i in range(len(x_data)):
                    ax[idx1,idx2].bar(x_data[i],np.round(y_data[i],1),color=color_tab[i],label=wk_lg[i])
                if idx2==0:  
                    ax[idx1,idx2].set_ylabel(yr,fontsize=20)
                ax[idx1,idx2].set_title(mnth,fontsize=20)
                ax[idx1,idx2].legend(title='Colors')
        fig.suptitle(f'MONTHLY BTC BALANCE COMPARISON FOR EACH YEAR',fontsize=24)
        fig.align_labels()
        fig.tight_layout();
