# -*- coding: utf-8 -*-
"""
Created on Thu Feb 20 15:00:14 2025
@author: win11
"""
import dash as dash
from dash import dcc, html, Input, Output, callback
import plotly.express as px
import plotly.graph_objects as go
#from data.dataprep import data_prep
import pandas as pd
import numpy as np
import dash_bootstrap_components as dbc
dbc_css = "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates/dbc.min.css"
#df_money_order, df_money_orderdetail = data_prep()
df_money_orderdetail =  pd.read_csv('orderdetails_money.csv')
#df_orderdetail_grouped = df_money_orderdetail.groupby(by = [df_money_orderdetail['orderDate'].dt.to_period('M').astype('str'), 'categoryName'])['nettoPrice'].sum().reset_index()      
def format_prefix_ticktext_fig_money(catname):
    if (catname == 'Total'):
        return f"<span style='font-weight:bold'> {catname}</span>   "
    else: return f"<span> {catname}</span>   "
    
#VISUAL TO CREATE HORIZONTAL BARCHART WITH ACTUAL VALUES    
def create_fig_money(data):
    #VISUAL PRINTS REVENUE PER CATEGORY THIS MONTH
    #INPUT IS DATA = SUMMARIZED CATEGORY, REVENUE, DELTA PM AND DELTA PM%
    
    #all tickvalues y axis in an array to use to format
    tv =  data['categoryName'].to_numpy()
    #format all ticks Y axis
    tt = [format_prefix_ticktext_fig_money(member) for member in tv]
    
    
    #ycoordinates for horizontal fake gridlines          
    #number of in between lines len(data) - 2: first line is black
    # in this case we have 8 rows + total rows = 9 rows (len(data)).
    # 9 rows is 8 in between rows, 1 row is black (the one above total), leaves 7
    # starting at y=1.5. Ok but since np.arrange skips the last value, we do
    #len(data) -1 :-) 
    
    #define the range
    y_coords = np.arange(1.5, len(data) - 1, 1)
    
    #calculate max category Nettoprice and make the max_x a bit larger
    #for displaying purposes (max K value not completely visible)
    
    max_x = 1.15 * (data.loc[data['categoryName'] != 'Total']['nettoPrice'].max())
    
    #customdata for extensive hovertemplate, show all data in tooltip bar.
    mycustomdata = np.stack((data['delta_pm_money'], data['delta_pm_perc']), axis=-1)
    
 
    
    
    fig = go.Figure()
    
    fig.add_trace(go.Bar(
    y=data['categoryName'],
    x=data['barLength'],
    text=data['nettoPrice'],
    orientation='h',
    #the bars
    marker=dict(
        color='rgba(64,64,64,1)',
        #cornerradius=15,
        line=dict(color='rgba(0, 0, 0, 1.0)', 
                  width=1)
        )
    ))
    #labels at the end of bar, lambda function to create a bold label for the total
    #hovertemplate: <extra></extra> hides this funny trace box
    fig.update_traces(texttemplate=data['categoryName'].apply(lambda x: '<b>%{text:.4s}</b>' if x == 'Total' else '%{text:,.3s}'),
                      textposition='outside',
                      customdata = mycustomdata,
                                      hovertemplate = '<b>%{y}</b><br><br>'+\
                                     'Revenue: %{x:,.3s}<br>'+\
                                     "Δ PM: %{customdata[0]:,.3s}<br>"+\
                                     "ΔPM%: %{customdata[1]: .3f}% <extra></extra>"
                      
                      
                      )
    
    fig.update_xaxes(showticklabels=False,  range = [0, max_x]
                     )
    
    #the generated pre formatted y ticks are used below, result: category Total is bold.
    
    fig.update_yaxes( dict(
        tickmode='array',
        tickvals=tv,
        ticktext=tt
    ),showgrid=False
       
        )
    
    
    
    fig.update_layout(
        margin=dict(l=5, r=5, t=25, b=5),
        plot_bgcolor='white',
        title = {
             'text': 'AC',
             'y':1, # new
             'x':0.5,
             'xanchor': 'center',
             'yanchor': 'top' # new
            },
        
        
        )
    
    
    
    #opacity + line_color make the lines appear nice on the white template as they are meant to be
    fig.add_hline(y=0.5, line_dash="solid", line_width=1, opacity=1, line_color="Black")
    
    for y in y_coords:
        fig.add_hline(y=y, line_dash="solid", line_width=1,  opacity=1, line_color='#ded6ca')
    
    #vertical black line after horizontal lines to put it on top of semigridlines
    fig.add_vline(x=0, line_dash="solid", line_width=1)
    
    return dcc.Graph(figure = fig)
#VISUAL TO DISPLAY CHANGE RELATED TO PREVIOUS MONTH IN NUMBERS
def create_fig_pm(data):
    
    #decide on the range x for the visual, the goals is to keep the x=0 line
    #at the same place on screen no matter the change.
    #the max of the absolute values is the basis for the boundary
    #min_x is the m irror. The value 1.4 is to make sure all labels incl .K are
    #shown on the screen
      
    max_x =1.4 * ( abs(data['delta_pm_money']).max())
    min_x = - max_x
        
    
    #define the range of y-coordinates for the horizontal fake gridlines
    y_coords = np.arange(1.5, len(data) - 1, 1)
    
    fig = go.Figure()
    fig.add_trace(go.Bar(
    y=data['categoryName'],
    x=data['delta_pm_money'],
    text=data['delta_pm_money'],
    orientation='h',
    hovertemplate = '%{y} <extra></extra>',
    marker=dict(
        color=data['delta_pm_money'].apply(lambda x: 'red' if x<0 else 'green'),
        line=dict(color=data['delta_pm_money'].apply(lambda x: 'red' if x<0 else 'green'), 
                  width=1)
        )
    ))
    #labels at the end of bar
    fig.update_traces(texttemplate=data['categoryName'].apply(lambda x: '<b>%{text:,.3s}</b>' if x == 'Total' else '%{text:,.3s}'), textposition='outside')
    
    #remove yaxis lables
    fig.update_xaxes(showticklabels=False, range = [min_x, max_x])
    fig.update_yaxes(showticklabels=False)
    fig.update_layout(
    title = {
         'text': 'Δ' + ' PM',
         'y':1, # new
         'x':0.5,
         'xanchor': 'center',
         'yanchor': 'top' # new
        },
    
    margin=dict(l=5, r=5, t=25, b=5),
    plot_bgcolor='white'
    )
    
    
    for y in y_coords:
        fig.add_hline(y=y, line_dash="solid", line_width=1,  opacity=1, line_color='#ded6ca')
        
        
    fig.add_hline(y=0.5, line_dash="solid", line_width=1, opacity=1, line_color='Black')
    
    #fig.update(layout_xaxis_range = [-100,100])
    fig.add_vline(x=0, line_dash="solid", line_width=1)
    
    return  dcc.Graph(figure = fig)
#VISUAL TO DISPLAY PERCENTAGE CHANGE WITH THE PREVIOUS MONTH
def create_pm_perc(data):
    #data = df.copy(deep=True)
    #df.groupby('Customer')['Date'].shift()
    #print(data[['categoryName','delta_pm_perc']].head(30))
    #data = data.loc[data['orderDate'] == '2015-04'].sort_values('nettoPrice', ascending=True)
    
    list_items = []
    
    for index, row in data.iterrows():
        color='red'
        fontweight = 'normal'
        value = row['delta_pm_perc']
        if row['delta_pm_perc'] > 0:
            value = '+' + str(row['delta_pm_perc'])
            color='green'
        if row['categoryName'] == 'Total':
            fontweight='bold'
        list_items.append(dbc.ListGroupItem(value, style={'fontStyle': 'italic', 'color': color,'fontWeight':fontweight , 'fontSize':'13px','lineHeight':'30px'}))
    #no idea why I need it but somehow the order is reverse in the histograms
    list_items.reverse()
    list_group = dbc.ListGroup(
        list_items,
    flush=True, style={'textAlign':'right'}
)
    
    return list_group
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.SANDSTONE,dbc_css]) 
app.layout = dbc.Container([ 
               dbc.Row([
                   dbc.Col([
                       html.P('Northwind Traders'),
                       html.P('Revenue by Product Category in K$'),
                       html.P(id='dynamic_title')
                       
                       ], style={'fontWeight': 'bold'}),
                   dbc.Col([
                       dbc.Select(
                           id="select_month",
                           options= df_money_orderdetail['orderDate'].unique(),
                           value= '2015-04'
                           )
                       ], className='col-md-4 col-sm-12'),
                                  
                   dbc.Col([
                       html.H2('IBCS Experiment no.1'),
                       ], className='col-md-4 col-sm-12')
                   ], style={'marginBottom': '2rem'}),
               
               dbc.Row([ 
                   dbc.Col([
                       html.Div(id='month_money'),
                       ], className='col-md-7 col-sm-12'),
                   dbc.Col([
                       html.Div(id='MoM_change')
                       ], className='col-md-3 col-sm-12'),
                   dbc.Col([
                       html.Div('\u0394' + ' PM%', style={'fontStyle': 'italic','textAlign':'right','paddingTop':'0px'}),
                       html.Div(id='MoM_change_perc' )
                       ], className='col-md-1 col-sm-12')
                   
                   ], className='col-md-12'),
],style={'marginTop': '3rem'},  fluid=False)
@app.callback( Output('month_money', 'children'),
               Output('MoM_change', 'children'),
               Output('MoM_change_perc', 'children'),
               Output('dynamic_title', 'children'),
               Input(component_id='select_month', component_property='value')
              )
def update_all(value):
    #the incoming dataframe has all total, delta pm and delta pm% for each month,
    #filter it on the selected value (=year-month), drop=True to remove the extra
    #created useless index column
     data = df_money_orderdetail.loc[(df_money_orderdetail['orderDate'].astype('str') == value)]\
         .sort_values(['nettoPrice'], ascending=[False]).reset_index(drop=True)
     
     #barLength is for all but the total which is added later the same value as NettoPrice
     #in IBSC total has no bar, the barLength will be 0
     
     data['barLength'] = data['nettoPrice']
     
     #define a dict with all the values belonging to a total record
     total_dict = {'categoryName': 'Total', 'orderDate': value, \
                   'nettoPrice': data['nettoPrice'].sum(), \
                   'delta_pm_money': data['delta_pm_money'].sum(), \
                   'delta_pm_perc': round(100 * (data['delta_pm_money'].sum()/(data['nettoPrice'].sum()+data['delta_pm_money'].sum())),1),\
                   'barLength': 0    
     }
     
         
     #add the total record at the end of the dataframe where it also has to appear on the screen
     data.loc[len(data)] = total_dict
     
     #reverse dataframe, the horizontal bars are drawn from the bottom of the
     #visual and ibcs wants it exactly the other way around, must be done after the latest
     #indexing and addition of totals
     data = data.iloc[::-1]
     #all orderdates are the same in the resulting dataframe data and I'm to lazy to pimp
     #the year month for this experiment
     title = data['orderDate'].min() + ' AC and PM'
     
     #update all visuals with the updated data
     return create_fig_money(data), create_fig_pm(data), create_pm_perc(data), title
app.run(debug=True)