import dataiku
from datetime import datetime
import dash
import dash_bootstrap_components as dbc
from dash import dash_table
import dash_daq as daq
import dash_interactive_graphviz
from process_mining.graph_creation import create_graph
from process_mining.process_mining import mine_process
from process_mining.filtering import *
from process_mining.variants import get_variants, variant_counts
from webapp.selectors import contained_intersecting, filter_title, start_step, end_step, case_performance_slider, filter_element, single_datetime_filter_picker, filter_element_toggle, filter_element_range
from webapp.attribute_selectors import attribute_filters
from webapp.variants import variants_container, variants_filter_object
from webapp.selected_elements import selected_node_window, selected_edge_window, selected_node_card, empty_card, selected_edge_card, empty_card_individual, selected_node_individual_card, selected_edge_individual_card
from webapp.styles import *
from webapp.legend import legend_object
from webapp.filter_processing import apply_filters
from webapp.explanation import process_mining_explanation, conformance_explanation
from webapp.header import filter_header, filter_subtitle, header_conformance_children, header_children, exclude_subtitle
from webapp.save_modal import save_modal
from webapp.process_management import save_process_in_dataset
from webapp.layout import layout_discovery, time_frequency, variants_conformance_tabs
from webapp.sink import sink_container, compute_conform_workflow
from data_management import get_managed_folder_id_with_folder_name, write_pickle_in_dss_folder, read_pickle_from_dss_folder
from webapp.formatting import human_format, formatted_human_readable, start_week
import pickle
import ast
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
from random import sample
from dash.exceptions import PreventUpdate
import logging
import threading
import time
from dataiku import insights
from flask import send_from_directory, session
from dash import html, dcc
from dash.dependencies import Input, Output, State
from flask_session import Session
from dataiku_filter_listener import DataikuFilterListener
from dash_extensions.enrich import html, Input, Output
from webapp.dashboard_filtering import apply_dashboard_filters

server = app.server
server.secret_key = 'BAD_SECRET_KEY'
server.config['SESSION_TYPE'] = 'filesystem'
server.config['SESSION_PERMANENT'] = False
Session(server)

def get_assets_folder():
    result = None
    for path_ in os.environ.get("PYTHONPATH", "").split(os.pathsep):
        if os.path.exists(os.path.join(path_,"assets")):
            result = os.path.join(path_,"assets")
    return result

@app.server.route("/api/<filename>")
def get_file(filename):
    return send_from_directory(get_assets_folder(), filename)

workflow_df = None
total_cases = None
activities_count_df = None
transitions_df = None
variants_df = None
    
def load_data():
    global workflow_df
    global total_cases
    global activities_count_df
    global transitions_df
    global variants_df
    
    logging.info("Current step: workflow_full")
    workflow = dataiku.Dataset("workflow_full")
    logging.info("Current step: dataframe")
    workflow_df = workflow.get_dataframe()
    workflow_df = workflow_df.sort_values(by=['sorting'])
    workflow_df['case'] = workflow_df['case'].apply(str)
    
    logging.info("Current step: variants_id")
    variants = dataiku.Dataset("variants_id")
    variants_df = variants.get_dataframe()
    
    logging.info("Current step: case")
    total_cases = len(workflow_df['case'].unique())
    
    logging.info("Current step: activity_count")
    activities_count = dataiku.Dataset("activity_count")
    activities_count_df = activities_count.get_dataframe()
    
    logging.info("Current step: transitions_frequency_share_prepared")
    transitions_frequency_share = dataiku.Dataset("transitions_frequency_share_prepared")
    transitions_df = transitions_frequency_share.get_dataframe()
    logging.info("Loading: finished")


logging.basicConfig(format="%(asctime)s: %(message)s", level=logging.DEBUG, datefmt="%H:%M:%S")
logging.info("Main    : before creating thread")

loading_thread = threading.Thread(target=load_data)
logging.info("Main    : before running loading data thread")
loading_thread.start()

palette = '#D5D9D9', '#3075AE', '#ff7e0b'

client = dataiku.api_client()
project_key = dataiku.default_project_key()
project = client.get_project(project_key)
variables = project.get_variables()

use_end_timestamp = variables['standard']['use_end_timestamp']
end_time_column = variables['standard']['end_timestamp']

palette = ['#BDD8ED', '#3075AE', '#4F934F']

@app.callback(Output('graph-legend', 'children'),
              Output('graph-workflow', 'children'),
              Output('gradient-legend', 'style'),
              Output('save-process-button', 'disabled'),
              Output('variants-container', 'style'),
              State('graph-workflow', 'children'),
              Input('variants-filter', 'value'),
              Input('time-frequency', 'value'),
              Input('variants-conformance-tabs', 'active_tab'))
def render_content(old_graph, nb_variants, frequency_time, variant_conformance):
    loading_thread.join()
    old_current_process = session.get('old_current_process')
    current_process = session.get('current_process')
    unique_nodes = session.get('unique_nodes')
    old_nb_variants = session.get('old_variants_filter')
    variants_count = session.get('variants_count')
    variants_data = session.get('variants_data')
    ctx = dash.callback_context
    if current_process is not None:
        if variant_conformance == 'conformance-tab':
            filtered_workflow = current_process
        else:
            filtered_workflow = filter_workflow(current_process, nb_variants, variants_count, variants_data)
        filtered_cases = len(current_process['case'].unique())
        
        if filtered_cases == 0:
            return [html.P('No cases filtered, try releasing the constraints', style={'font-size': '12px', 'margin': '1px'})], legend_object(1, 1, frequency_time == 'time'), dbc.Col([None], style={'width': '600px'}), {'height': '50%',
               'width': '100%',
               'margin': 'auto',
               'background': 'linear-gradient(to right, #F5F0F9, #A47BC7)',
               'padding': 0}
        
        if variant_conformance == 'conformance-tab':
            workflow_mined, start_end, activities = compute_conform_workflow(filtered_workflow, frequency_time, use_end_timestamp)
        else:
            if frequency_time == 'frequency':
                workflow_mined, start_end, activities = mine_process(filtered_workflow, transition='frequency')
            else:
                if use_end_timestamp:
                    workflow_mined, start_end, activities = mine_process(filtered_workflow, 'end_timestamp', transition='performance')
                else:
                    workflow_mined, start_end, activities = mine_process(filtered_workflow, transition='performance')
        graph, unique_nodes, max_count = create_graph(workflow_mined, start_end, activities, transition=frequency_time)
        session['graph'] = graph
        graph_object = dash_interactive_graphviz.DashInteractiveGraphviz(
            id="filtered-workflow",
            dot_source=graph,
            style={'width': '90%', 'height': '85%',
                  'cursor': 'pointer'}
        )
        session['final_process'] = filtered_workflow
        session['old_current_process'] = current_process
        if frequency_time=='time':
            legend_style = {
                'display': 'inline-block',
                'height': '20px',
                'width': '100px',
                'background': 'linear-gradient(to right, #F5F0F9, #A47BC7)',
                'margin-right': '5px',
                'vertical-align': 'middle',
                'margin-bottom': '16px'
            }
        else:
            legend_style = {
                            'display': 'inline-block',
                            'height': '20px',
                            'width': '100px',
                            'background': 'linear-gradient(to right, #F1F8FD, #7DBEEA)',
                            'margin-right': '5px',
                            'vertical-align': 'middle',
                            'margin-bottom': '16px'
                        }
        session['unique_nodes'] = unique_nodes
        session['old_variants_filter'] = nb_variants
        return legend_object(1 if frequency_time=='frequency' else 0, max_count, frequency_time), dbc.Col([graph_object], style={'width': '600px', 'height': '500px'}), legend_style, (variant_conformance == 'conformance-tab'), {'display': 'none'} if (variant_conformance == 'conformance-tab') else {'margin-top': '6px'}
    else:
        raise PreventUpdate

@app.callback(Output('variants-container', 'children'),
              Input('dfl', 'filters'))
def render_content(filters):
    variants = session.get('old_variants_filter')
    logging.info('Current step: Joining the thread')
    loading_thread.join()
    logging.info('Current step: Thread joined')
    
    if filters:
        filtered_workflow = apply_dashboard_filters(workflow_df, filters)
    else:
        filtered_workflow = workflow_df.copy()
    
    variants_data = get_variants(filtered_workflow)
    variants_count = variant_counts(variants_data)
    variants_count.sort_values('case', ascending=False, inplace=True)
    
    session['variants_data'] = variants_data
    session['variants_count'] = variants_count
    
    if variants is None:
        variants = 5
    
    session['current_process'] = filtered_workflow
    return variants_filter_object(variants, len(session['variants_count']))

@app.callback(Output('selected', 'children'),
              Output('selected', 'style'),
              Input('filtered-workflow', 'selected_node'),
              Input('filtered-workflow', 'selected_edge'),
              Input('card-close-button', 'n_clicks'))
def output_selected(selected_node, selected_edge, n_close):
    data = session.get('unique_nodes')
    ctx = dash.callback_context

    if not ctx.triggered:
        raise PreventUpdate
    
    button = ctx.triggered[0]["prop_id"]
    if button == 'card-close-button.n_clicks':
        return empty_card(), {'position': 'absolute',
                              'z-index': '999',
                              'display': 'None'}
    if selected_node is not None:
        node = data[int(selected_node)]
        return selected_node_card(node, selected_node, data, 
                                  activities_count_df, transitions_df, 
                                  total_cases), {
                      'z-index': '999',
                      'display': 'block',
                      'margin-top': '10px',
                      'margin-right': '10px',
                      'border-radius': 0,
                      'border-left': '5px solid #69B2E6',
                      'float': 'right'}
    elif selected_edge is not None:
        nodes = selected_edge.split('->')
        source = data[int(nodes[0])]
        target = data[int(nodes[1])]
        return selected_edge_card(source, target, nodes, 
                                  data, transitions_df, 
                                  total_cases), {
                      'z-index': '999',
                      'display': 'block',
                      'margin-top': '10px',
                      'margin-right': '10px',
                      'border-radius': 0,
                      'border-left': '5px solid #A07DC2',
                      'float': 'right'}
    else:
        return empty_card(), {'position': 'absolute',
                              'z-index': '999',
                              'display': 'None'}


@app.callback(
    Output("save-process-modal", "is_open"),
    Input("cancel-save-process", "n_clicks"),
    Input("modal-save-process", "n_clicks"),
    Input("save-process-button", "n_clicks"),
    State("save-process-modal", "is_open"))
def toggle_open_modal(cancel_button, modal_save_button, save_button, is_open):
    ctx = dash.callback_context
    if ctx.triggered[0]['prop_id'] == 'modal-save-process.n_clicks':
        if modal_save_button > 0:
            save_process_in_dataset(project, session['final_process'], variants_df)
    if cancel_button or modal_save_button or save_button:
        return not is_open
    return is_open


@app.callback(
    Output("save-modal", "is_open"),
    Output("modal-save-viz", "disabled"),
    Output("export-viz-name", "data"),
    Input("cancel-save", "n_clicks"),
    Input("modal-save-viz", "n_clicks"),
    Input("save-viz", "n_clicks"),
    Input('viz-name', 'value'),
    State("save-modal", "is_open"))
def toggle_open_modal(cancel_button, modal_save_button, save_button, name, is_open):
    ctx = dash.callback_context
    if name is None:
        disabled = True
    else:
        if len(name) > 0:
            disabled = False
        else:
            disabled = True
    if ctx.triggered[0]['prop_id'] == 'modal-save-viz.n_clicks':
        if len(name) > 0 and modal_save_button > 0:
            return is_open, disabled, name
    if ctx.triggered[0]['prop_id'] == 'viz-name.value':
        return is_open, disabled, ""
    if cancel_button or modal_save_button or save_button:
        return not is_open, disabled, ""
    return is_open, disabled, ""

app.clientside_callback(
    """
    function placeholder(n_clicks) {
        if (n_clicks > 0) {
            window.parent.WT1SVC.event('sol-action',
                {
                    'action_type': 'button',
                    'action_name': 'save_viz',
                    'solution_name': 'process_mining'
                });
        }
    };
    """,
    Output("dummy-event2", "children"),
    Input("modal-save-viz", "n_clicks")
)

app.clientside_callback(
    """
    function placeholder(n_clicks) {
        if (n_clicks > 0) {
            window.parent.WT1SVC.event('sol-action',
                {
                    'action_type': 'button',
                    'action_name': 'save_process',
                    'solution_name': 'process_mining'
                });
        }
    };
    """,
    Output("dummy-event3", "children"),
    Input("modal-save-process", "n_clicks")
)

app.clientside_callback(
    """
    function placeholder(n_clicks) {
        if (n_clicks > 0) {
            window.parent.WT1SVC.event('sol-action',
                {
                    'action_type': 'button',
                    'action_name': 'browse_node_traces',
                    'solution_name': 'process_mining'
                });
        }
    };
    """,
    Output("dummy-event5", "children"),
    Input("browse-node-traces", "n_clicks")
)

app.clientside_callback(
    """
    function placeholder(n_clicks) {
        if (n_clicks > 0) {
            window.parent.WT1SVC.event('sol-action',
                {
                    'action_type': 'button',
                    'action_name': 'export_dataset',
                    'solution_name': 'process_mining'
                });
        }
    };
    """,
    Output("dummy-event6", "children"),
    Input("modal-export-traces", "n_clicks")
)

layout_mining = dbc.Container([
    html.Script("""console.log("send_log");
                window.parent.WT1SVC.event('bs-process-mining-webapp-loaded');""", type="text/javascript"),
    DataikuFilterListener(logging=True, id="dfl"),
    html.Div(id='download-trigger', style={'display': 'none'}),
    variants_conformance_tabs,
    variants_container(),
    time_frequency,
    dbc.Col(id='tab-content', children=layout_discovery(project_key)),
    dbc.Tooltip(process_mining_explanation,
            id='tooltip-content',
            target="question-tooltip",
            style={'color': '#bbbbbb',
                  'font-size': '10px'}),
    html.Div(id='dummy-event2'),
    html.Div(id='dummy-event3'),
    html.Div(id='dummy-event5'),
    html.Div(id='dummy-event6'),
    dcc.Store(id='dummy-input', data=1),
    dcc.Store(id='dummy-output', data=1),
    dcc.Store(id='discovery-nb-cases', data=0),
    dcc.Store(id='export-viz-name', data="")],
             style={'font-size': '12px', 'height': '100%', 'padding': 0}, fluid=True)
logging.info("Current step: layout_mining, done")

flow = project.get_flow()
export_zone = [zone for zone in flow.list_zones() if zone.name=='Exports'][0]

def filter_traces_activity(node, node_data, filter_type):
    activity = node_data[int(node)]
    filtered_workflow = workflow_df[workflow_df['activity']==activity]
    if filter_type == 'full':
        filtered_workflow = workflow_df[workflow_df['case'].isin(filtered_workflow['case'].unique())]
    return filtered_workflow

def filter_traces_transition(transition, node_data, filter_type):
    source = node_data[int(transition[0])]
    target = node_data[int(transition[1])]
    processed_workflow = workflow_df.sort_values(['case', 'sorting'])
    processed_workflow['previous_activity'] = processed_workflow.groupby('case')['activity'].shift(1)
    filtered_workflow = processed_workflow[(processed_workflow['previous_activity']==source) & (processed_workflow['activity']==target)]
    if filter_type == 'full':
        filtered_workflow = processed_workflow[processed_workflow['case'].isin(processed_workflow['case'].unique())]
    return filtered_workflow

@app.callback(
    Output("export-modal", "is_open"),
    Output("modal-export-traces", "disabled"),
    Output("link-loading", "children"),
    Output("modal-export-traces", "style"),
    Input("cancel-export", "n_clicks"),
    Input("modal-export-traces", "n_clicks"),
    Input("browse-node-traces", "n_clicks"),
    Input("export-name", "value"),
    State("export-traces-input", "value"),
    State("export-modal", "is_open"),
    State('filtered-workflow', 'selected_node'))
def toggle_open_modal(cancel_button, modal_export_button, export_button, name, export_traces_input, is_open, node):
    node_data = session.get('unique_nodes')
    ctx = dash.callback_context
    if name is None:
        disabled = True
    else:
        if len(name) > 0:
            disabled = False
        else:
            disabled = True
    if ctx.triggered[0]['prop_id'] == 'modal-export-traces.n_clicks':
        if len(name) > 0 and modal_export_button > 0:
            # export dataset
            filtered_dataset = filter_traces_activity(node, node_data, export_traces_input)
            dataset = project.get_dataset("workflow_clean")
            settings = dataset.get_settings()
            params = settings.settings['params']
            if 'path' in params:
                params['path'] = params['path'].replace('workflow_clean', name)
                dataset = project.create_dataset(dataset_name=name, type=settings.type,
                                 params=params, formatType=settings.settings['formatType'],
                                 formatParams=settings.settings['formatParams'])
            else:
                params['table'] = params['table'].replace('workflow_clean', name)
                dataset = project.create_dataset(dataset_name=name, type=settings.type, params=params)
            new_settings = dataset.get_settings()
            new_settings.settings["managed"] = True
            new_settings.save()
            dataset.move_to_zone(export_zone)
            dataset_df = dataiku.Dataset(name)
            dataset_df.write_with_schema(filtered_dataset)
            return is_open, disabled, html.A(dbc.Button(
                        "Open",
                        id="modal-open-dataset",
                        className="sm",
                        n_clicks=0),
                        target="_blank",
                        href="/projects/" + project_key + "/datasets/" + name + "/explore/"
                ), {'display': 'None'}
    if ctx.triggered[0]['prop_id'] == 'export-name.value':
        return is_open, disabled, html.Div(), {}
    if cancel_button or export_button or modal_export_button:
        return not is_open, disabled, html.Div(), {}
    return is_open, disabled, html.Div(), {}

@app.callback(
    Output("export-modal-edge", "is_open"),
    Output("modal-export-traces-edge", "disabled"),
    Output("link-loading-edge", "children"),
    Output("modal-export-traces-edge", "style"),
    Input("cancel-export-edge", "n_clicks"),
    Input("modal-export-traces-edge", "n_clicks"),
    Input("browse-edge-traces", "n_clicks"),
    Input("export-name-edge", "value"),
    State("export-traces-input-edge", "value"),
    State("export-modal-edge", "is_open"),
    State('filtered-workflow', 'selected_edge'),)
def toggle_open_modal(cancel_button, modal_export_button, export_button, name, export_traces_input, is_open, edge):
    node_data = session.get('unique_nodes')
    ctx = dash.callback_context
    if name is None:
        disabled = True
    else:
        if len(name) > 0:
            disabled = False
        else:
            disabled = True
    if ctx.triggered[0]['prop_id'] == 'modal-export-traces-edge.n_clicks':
        if len(name) > 0 and modal_export_button > 0:
            # export dataset
            edge = edge.split('->')
            filtered_dataset = filter_traces_transition(edge, node_data, export_traces_input)
            dataset = project.get_dataset("workflow_clean")
            settings = dataset.get_settings()
            params = settings.settings['params']
            params['path'] = params['path'].replace('workflow_clean', name)
            dataset = project.create_dataset(dataset_name=name, type=settings.type,
                                 params=params, formatType=settings.settings['formatType'],
                                 formatParams=settings.settings['formatParams'])
            new_settings = dataset.get_settings()
            new_settings.settings["managed"] = True
            new_settings.save()
            dataset.move_to_zone(export_zone)
            dataset_df = dataiku.Dataset(name)
            dataset_df.write_with_schema(filtered_dataset)
            return is_open, disabled, html.A(dbc.Button(
                        "Open",
                        id="modal-open-dataset",
                        className="sm",
                        n_clicks=0),
                        target="_blank",
                        href="/projects/" + project_key + "/datasets/" + name + "/explore/"
                ), {'display': 'None'}
    if ctx.triggered[0]['prop_id'] == 'export-name-edge.value':
        return is_open, disabled, html.Div(), {}
    if cancel_button or export_button or modal_export_button:
        return not is_open, disabled, html.Div(), {}
    return is_open, disabled, html.Div(), {}

# Callback to trigger the download
app.clientside_callback(
    """
    function(name) {
        if (name.length > 0) {
            const element = document.getElementById('filtered-workflow');
            html2canvas(element).then(function(canvas) {
                const link = document.createElement('a');
                link.download = name + '.png';
                link.href = canvas.toDataURL('image/png');
                link.click();
            });
        }
        return '';
    }
    """,
    Output('download-trigger', 'children'),
    Input('export-viz-name', 'data'),
    prevent_initial_call=True
)

# build your Dash app
app.config.external_stylesheets = [dbc.themes.BOOTSTRAP, "https://use.fontawesome.com/releases/v5.13.0/css/all.css", "api/app.css"]
app.config.external_scripts = ["https://html2canvas.hertzen.com/dist/html2canvas.min.js"]
app.layout = layout_mining


