from datetime import datetime
import pandas as pd
import numpy as np
import streamlit as st
from st_aggrid import AgGrid, GridUpdateMode, JsCode

from backend.db import DB, Session
import backend.his_io as his_io
from backend.helpers import reserved_columns, update_columns
from session_ui import session_ui
from stats_ui import stats_ui


st.set_page_config(layout='wide')


# Open database
try:
    db = DB()
except Exception as e:
    st.error('Datenbank konnte nicht geladen werden.')
    st.exception(e)
    st.stop()


# Init session state
if 'session' not in st.session_state:
    st.session_state.session = None
if 'last_session_token' not in st.session_state:
    st.session_state.last_session_token = None


# Load session
def load_session() -> Session:
    session = session_ui(db, st.session_state.session)
    return session


def store_session(session: Session):
    session.edited = datetime.now()
    db.save_session(session)

st.session_state.session = load_session()
if st.session_state.session is None:
    st.stop()

# Save and export
@st.cache_data
def export(points_df: pd.DataFrame, his_header: pd.DataFrame, exercise_columns: list[str], grades_table: pd.DataFrame):
    return his_io.export(points_df, his_header, exercise_columns, grades_table)

# Set some default UI values from the session 
if st.session_state.session.token != st.session_state.last_session_token:
    st.session_state.grade_range = (st.session_state.session.grade_4_0, st.session_state.session.grade_1_0)
    st.session_state.max_points = st.session_state.session.max_points
    st.session_state.last_session_token = st.session_state.session.token
    #export.clear()


st.write(f':blue[{st.session_state.session.table_name}]')
st.write(f':gray[Erstellt:] {st.session_state.session.created.strftime("%d.%m.%Y %H:%M:%S")}     :gray[Zuletzt bearbeitet:] {st.session_state.session.edited.strftime("%d.%m.%Y %H:%M:%S")}')

exercise_columns = [col for col in st.session_state.session.points_df.columns if col not in reserved_columns+list(st.session_state.session.students_df.columns)]
if len(exercise_columns) == 0:
    exercise_columns = ''
exercise_columns = st.text_input('Aufgaben', value=','.join(exercise_columns), placeholder='1,2,3,4,...')
exercise_columns = [col.strip() for col in exercise_columns.split(',') if col.strip()]

st.session_state.session.points_df = update_columns(exercise_columns, st.session_state.session.points_df)


# Grade calculation
with st.expander('Notenberechnung'):
    grades = ['1,0', '1,3', '1,7', '2,0', '2,3', '2,7', '3,0', '3,3', '3,7', '4,0', '5,0']
    left, right = st.columns(2, gap='large')
    with left:
        grade_4_0, grade_1_0 = st.slider('Notenbereich (4,0-Grenze / 1,0-Grenze)', min_value=0., max_value=1., key='grade_range')
        max_points = st.slider('Maximalpunktzahl', min_value=1, max_value=100, key='max_points')

    if st.session_state.session.grade_4_0 != grade_4_0 or st.session_state.session.grade_1_0 != grade_1_0 or st.session_state.session.max_points != max_points:
        st.session_state.session.grade_4_0 = grade_4_0
        st.session_state.session.grade_1_0 = grade_1_0
        st.session_state.session.max_points = max_points


    def calculate_grades_table(grade_1_0, grade_4_0):
        grade_interval = (grade_1_0 - grade_4_0) / 9
        grade_intervals = [grade_1_0 - i * grade_interval for i in range(10)]
        grade_intervals.append(0)
        lower = np.array([i * st.session_state.session.max_points for i in grade_intervals])
        lower = np.ceil(lower * 2) / 2
        upper = np.roll(lower, 1)-0.5
        upper[0] = st.session_state.session.max_points
        return pd.DataFrame({'Note': grades, 'Prozent': grade_intervals, 'Untergrenze': lower, 'Obergrenze': upper})

    with right:
        grades_table = calculate_grades_table(st.session_state.session.grade_1_0,  st.session_state.session.grade_4_0)
        st.dataframe(grades_table,
                    hide_index=True,
                    column_config={
                        'Note': st.column_config.TextColumn(),
                        'Prozent': st.column_config.NumberColumn(format='%.2f'),
                        'Untergrenze': st.column_config.NumberColumn(format='%.1f'),
                        'Obergrenze': st.column_config.NumberColumn(format='%.1f')})

def calculate_points(session: Session):
    points = session.points_df[exercise_columns].sum(axis=1, skipna=False, min_count=1)
    session.points_df['Punkte'] = points
    session.points_df['Note'] = session.points_df['Punkte'].apply(lambda x: np.nan if np.isnan(x) else grades_table.iloc[np.digitize(x, grades_table['Untergrenze'])]['Note'])

calculate_points(st.session_state.session)

# Editable points table
def color_try_column(col):
    return ['color: red' if val > 2 else 'color: orange' if val > 1 else '' for val in col]

def color_grade_column(col):
    return ['color: red' if val in ['5,0'] else '' for val in col]


attemptCellStyle = JsCode(
    r"""
    function(params) {
        if (params.value == 2)
            return {'color': 'gold'};
        else if (params.value > 2)
            return {'color': 'red'};
        return {};
    }
    """)

gradeCellStyle = JsCode(
    r"""
    function(params) {
        if (params.value === '5,0') 
            return {'color': 'red'};
        return {};
    }
    """)

def points_table(session: Session, exercise_columns: list[str]):

    filterText = st.text_input('Filter', placeholder='Filter...', label_visibility='hidden')
    grid_options = {
        "columnDefs":[
            {"headerName":"Matrikelnummer","field":"Matrikelnummer","checkboxSelection": True, "cellDataType": "Number"},
            {"headerName":"Nachname","field":"Nachname", "cellDataType": "String"},
            {"headerName":"Vorname","field":"Vorname", "cellDataType": "String"},
            {"headerName":"Versuch","field":"Versuch", "cellDataType": "Number", "cellStyle": attemptCellStyle},
            {"headerName":"Punkte","field":"Punkte", "cellDataType": "Number"},
            {"headerName":"Note","field":"Note", "cellDataType": "Number", "cellStyle": gradeCellStyle},
            *[{ "headerName": col, "field": col, "cellDataType": "Number", "width": 40} for col in exercise_columns]
            ],
        "autoSizeStrategy": {"type":"fitCellContents"},
        "rowSelection":"single",
        "rowMultiSelectWithClick": True,
        "suppressRowDeselection": True,
        "suppressRowClickSelection": True,
        "quickFilterText": filterText
    }
    
    event = AgGrid(data=session.points_df.reset_index(), gridOptions=grid_options, key='grid1', update_mode=GridUpdateMode.SELECTION_CHANGED, allow_unsafe_jscode=True)
    return event

def points_editor(row_df: pd.DataFrame, exercise_columns: list[str]):
    grid_options = {
        "columnDefs":[
            {"headerName":"Matrikelnummer","field":"Matrikelnummer", "cellDataType": "Number"},
            {"headerName":"Nachname","field":"Nachname","cellDataType": "String"},
            {"headerName":"Vorname","field":"Vorname", "cellDataType": "String"},
            *[{ "headerName": col, "field": col, "cellDataType": "Number", "width": 40, "editable": True} for col in exercise_columns]
            ],
        "autoSizeStrategy": {"type":"fitCellContents"},
    }
    editor_event = AgGrid(data=row_df, gridOptions=grid_options, height=100, key='grid2'+str(row_df['Matrikelnummer']), update_mode=GridUpdateMode.VALUE_CHANGED)
    return editor_event
    


with st.expander('Punkteeingabe', expanded=True):

    event = points_table(st.session_state.session, exercise_columns)

    if event.selected_rows is not None:
        selected_row = event.selected_rows.astype(st.session_state.session.points_df.dtypes)
        selected_row_idx = int(event.selected_rows_id[0])
        with st.form('edit_points'):
            editor_event = points_editor(selected_row, exercise_columns)
            submit = st.form_submit_button('Submit')
            if submit:
                # Replace all commas with decimal points in editor_event.data
                data = editor_event.data
                data[exercise_columns] = data[exercise_columns].replace('\\,', '.', regex=True)
                # Submit updated row
                updated_row = data.astype(st.session_state.session.points_df.dtypes).set_index('Matrikelnummer')
                st.session_state.session.points_df.update(updated_row)
                st.session_state.request_rerun = True



# Statistics
stats_ui(st.session_state.session, grades_table, grades)


# Download button
st.download_button('Export nach :blue[HISinOne]', data=export(st.session_state.session.points_df, st.session_state.session.his_header, exercise_columns,grades_table), file_name=f'{st.session_state.session.table_name}_Noten.xlsx', mime='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')


# Store session
calculate_points(st.session_state.session)
store_session(st.session_state.session)

# Rerun if requested
if 'request_rerun' in st.session_state:
    del st.session_state['request_rerun']
    st.rerun()