import pandas as pd
import streamlit as st
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from api.hdat_reader import HDatReader, FitBit
from datetime import datetime as dt
from datetime import time


def initial_read():
    reader = HDatReader(
        'P:\\git\\health-smart-mirror\\apple_health_export\\HKCategoryTypeIdentifierSleepAnalysis_2022-09-257_20-35-49_SimpleHealthExportCSV.csv')
    return reader.read_sleep_data()


fitbit_data = FitBit('P:\\git\\health-smart-mirror\\fitbit_data')

st.set_page_config(
    layout='wide'
)

class App:
    def __init__(self):
        self.data = initial_read()


    def display_users_calories(self):
        df = fitbit_data.daily_calories()
        result = df[df['Id'] == st.session_state.selected_user]
        daily_calories = px.bar(result, x='ActivityDay', y='Calories')
        df = fitbit_data.hourly_calories()
        result = df[(df['Id'] == st.session_state.selected_user)]
        result.loc[:, 'ActivityHour'] = pd.to_datetime(result['ActivityHour'])

        date = st.date_input('Select a date', result['ActivityHour'].min(),
                             min_value=result['ActivityHour'].min(),
                             max_value=result['ActivityHour'].max())
        start = st.time_input('Select a starting hour', value=time())
        end = st.time_input('Select an ending hour', time(23, 59, 59))

        start = dt.combine(date, start)
        end = dt.combine(date, end)

        result = result[(result['ActivityHour'] >= start) &
                        (result['ActivityHour'] <= end)]
        hourly_calories = px.bar(result, x='ActivityHour', y='Calories')
        daily_calories.update_layout(title='Daily Calories')
        hourly_calories.update_layout(title='Hourly Calories')

        return daily_calories, hourly_calories

    def display_daily_steps(self):
        df = fitbit_data.daily_steps()
        result = df[df['Id'] == st.session_state.selected_user]
        bar = px.bar(result, x='ActivityDay', y='StepTotal')
        bar.update_layout(title='Daily Steps')
        return bar

    def display_day_sleep(self):
        df = fitbit_data.day_sleep()
        df['TotalMinutesAsleep'] = pd.to_numeric(df['TotalMinutesAsleep'])
        df['SleepHours'] = df['TotalMinutesAsleep'] / 60
        result = df[df['Id'] == st.session_state.selected_user]
        if result.empty:
            return None
        result.loc[:, 'SleepDay'] = pd.to_datetime(result['SleepDay'])
        start = st.date_input('Select a date', value=result['SleepDay'].min(),
                             min_value=result['SleepDay'].min(),
                             max_value=result['SleepDay'].max(), key='day_start_sleep_date')
        end = st.date_input('Select an end date', value=result['SleepDay'].max(), min_value=start,
                            max_value=result['SleepDay'].max(), key='day_end_sleep_date')

        result = result[(result['SleepDay'].dt.date >= start) & (result['SleepDay'].dt.date <= end)]
        fig = go.Figure()
        fig.add_trace(go.Bar(x=result['SleepDay'], y=result['TotalMinutesAsleep'] / 60, name='Hours Asleep',
                             hovertemplate="Hours=%{y}"))
        fig.add_trace(go.Bar(x=result['SleepDay'], y=result['TotalTimeInBed'] / 60, name='Time In Bed'))
        fig.update_xaxes(title_text='Date')
        fig.update_yaxes(title_text='Hours')
        fig.update_layout(title='Sleep Data')

        return fig

    def display_user_intensities(self):
        df = fitbit_data.daily_intensities()

        user_data = df[df['Id'] == st.session_state.selected_user]
        user_data.loc[:, 'ActivityDay'] = pd.to_datetime(user_data['ActivityDay'])
        start = st.date_input('Select a start date', value=user_data['ActivityDay'].min(), min_value=user_data['ActivityDay'].min(),
                      max_value=user_data['ActivityDay'].max())
        end = st.date_input('Select an end date', value=user_data['ActivityDay'].max(), min_value=start,
                      max_value=user_data['ActivityDay'].max())

        user_data = user_data[(user_data['ActivityDay'].dt.date >= start) & (user_data['ActivityDay'].dt.date <= end)]
        fig = go.Figure()
        fig.add_trace(go.Bar(x=user_data['ActivityDay'], y=user_data['SedentaryMinutes'], name='Sedentary'))
        fig.add_trace(go.Bar(x=user_data['ActivityDay'], y=user_data['LightlyActiveMinutes'], name='Lightly Active'))
        fig.add_trace(go.Bar(x=user_data['ActivityDay'], y=user_data['FairlyActiveMinutes'], name='Fairly Active'))
        fig.add_trace(go.Bar(x=user_data['ActivityDay'], y=user_data['VeryActiveMinutes'], name='Very Active'))
        fig.update_xaxes(title_text='Date')
        fig.update_yaxes(title_text='Minutes')
        fig.update_layout(title='Daily User Intensities')

        return fig

    def display_user_weight(self):
        df = fitbit_data.weight_log_info()

        user_data = df[df['Id'] == st.session_state.selected_user]
        if user_data.empty:
            return None

        fig = go.Figure()
        fig.add_hrect(y0=18.5, y1=24.9, line_width=0, fillcolor='green', opacity=0.1, name='Normal weight')
        fig.add_trace(go.Scatter(x=user_data['Date'], y=user_data['WeightPounds'], name='Weight', line=dict(width=2), yaxis='y2'))
        fig.add_trace(go.Scatter(x=user_data['Date'], y=user_data['BMI'], name='BMI', line=dict(width=2, dash='dash')))

        fig.update_layout(title='BMI and Mass', xaxis=dict(title='Date'), yaxis2=dict(title='lbs', side='right', overlaying='y'), yaxis=dict(title='kg/m²'))

        return fig

    def avg_sleep(self):
        df = fitbit_data.day_sleep()
        df['TotalMinutesAsleep'] = pd.to_numeric(df['TotalMinutesAsleep'])
        df['SleepHours'] = df['TotalMinutesAsleep'] / 60
        result = df[df['Id'] == st.session_state.selected_user]
        if result.empty:
            return np.nan, 'No sleep data available', 'off'
        result.loc[:, 'SleepDay'] = pd.to_datetime(result['SleepDay'])
        sleep = result['SleepHours'].mean()
        return round(sleep, 2), f'{round(sleep - 8, 2)} hours'

    def avg_steps(self):
        df = fitbit_data.daily_steps()
        result = df[df['Id'] == st.session_state.selected_user]
        if result.empty:
            return np.nan
        steps = int(result['StepTotal'].mean())
        return steps, f'{steps - 4774} compared to the US average'

    def avg_calories(self):
        df = fitbit_data.daily_calories()
        result = df[df['Id'] == st.session_state.selected_user]
        if result.empty:
            return np.nan
        return round(result['Calories'].mean(), 2)

    def mass(self):
        df = fitbit_data.weight_log_info()
        result = df[df['Id'] == st.session_state.selected_user]
        if result.empty:
            return np.nan
        return round(result[result['Date'] == result['Date'].max()]['WeightPounds'], 2)

    def bmi(self):
        df = fitbit_data.weight_log_info()
        result = df[df['Id'] == st.session_state.selected_user]
        if result.empty:
            return np.nan, 'No data available', 'off'
        bmi = round(result[result['Date'] == result['Date'].max()]['BMI'], 2)
        bmi = float(bmi)
        if bmi < 18.5:
            return bmi, 'Underweight', 'off'
        elif 18.5 <= bmi <= 24.9:
            return bmi, 'Normal', 'normal'
        elif 24.9 < bmi <= 29.9:
            return bmi, 'Overweight', 'off'
        else:
            return bmi, 'Obese', 'inverse'

    def run(self):
        def set_selected_user():
            st.session_state.selected_user = demo[st.session_state.selected_username]

        def add_goal():
            st.session_state.daily_goals[st.session_state.selected_user].append(st.session_state.inp_goal_task)

        def clear_goals():
            st.session_state.daily_goals[st.session_state.selected_user].clear()

        demo = {
            'John': 1503960366,
            'Smith': 6962181067,
            'Jake': 7086361926,
            'Arnold': 8792009665,
            'Max': 8877689391
        }

        if 'selected_user' not in st.session_state:
            st.session_state.selected_user = demo['John']

        if 'daily_goals' not in st.session_state:
            st.session_state.daily_goals = {
                1503960366: ['Workout', 'Reach 10000 Steps'],
                6962181067: [],
                7086361926: ['Go to the gym', 'Do high intensity workouts'],
                8792009665: ['Go to bed earlier', 'Adjust calorie intake'],
                8877689391: ['Lower BMI']
            }

        st.markdown('### Select a profile')
        st.selectbox('Select a user ID', options=demo, key='selected_username',
                                      on_change=set_selected_user)

        st.markdown('### Tasks and Goals\n' + '\n'.join([f'- {item}' for item in st.session_state.daily_goals[st.session_state.selected_user]]))
        c1, _, _ = st.columns(3)
        with c1:
            st.text_input('Input new goal/ task', key='inp_goal_task', on_change=add_goal)
            st.button('Clear goal', on_click=clear_goals, key='clr_goal_btn')

        tabs = st.empty()
        overview, calories, steps, intensities, sleep, weight = tabs.tabs(['Overview', 'Calories',
                                                                 'Steps', 'Intensities', 'Sleep', 'Weight'])

        with overview:
            st.subheader('Health Overview')

            c1, c2, c3 = st.columns(3)
            with c1:
                st.metric('Average Sleep (Hours)', *self.avg_sleep())
            with c2:
                st.metric('Average Daily Steps', *self.avg_steps(), help='US average from healthline.com')
                st.metric('Weight', self.mass())
            with c3:
                st.metric('Daily Caloric Intake', self.avg_calories(), round(self.avg_calories() - 2000, 2),
                          help='Based on daily 2000 calories')
                st.metric('BMI', *self.bmi())

        with calories:
            cc1, cc2 = st.columns(2)
            with cc1:
                st.markdown(
                    """
                    ### About Calories
                    - Women are recommended to consume 2,000 calories a day
                    - Men are recommended to consume 2,500 calories a day
                    - Calorie consumption is relative. You should use a calorie calculator online to find what is right
                        for you based on height, age, activity level, etc.
                    """
                )
                st.subheader('Hourly consumed calorie controls')
                fig, fig2 = self.display_users_calories()
            with cc2:
                st.plotly_chart(fig2)
                st.plotly_chart(fig)

        with steps:
            fig = self.display_daily_steps()
            c1, c2 = st.columns(2)
            with c1:
                st.markdown(
                    """
                    ### About Steps
                    - ~7,500 steps per day can help reduce the risk of death
                    - The CDC recommends ~30,000 steps per week, with a minimum of 15,000
                    - You should aim for at least 2,000 steps per day to meet CDC minimum guidelines
                    - If you're not sure how to get started try:
                        - Using the stairs instead of the elevator
                        - Talk a short walk during breaks
                        - Park farther away when running errands
                        - Perform housekeeping activities
                    
                    Sources: healthline.com, Harvard
                    """
                )
            with c2:
                st.plotly_chart(fig)

        with intensities:
            c1, c2 = st.columns(2)

            with c1:
                st.markdown(
                    """
                    ### About Intensities
                    - Intensities are measures of how active you were throughout your day.
                    - Sedentary is synonymous with being idle while very active is at the opposite end of that spectrum.
                    - As a general goal, it is recommended by the Mayo clinic to aim for **30 minutes** of active exercise a day.
                    """
                )
                st.subheader('Date controls')
                fig = self.display_user_intensities()
            with c2:
                st.plotly_chart(fig)

        with sleep:
            sc1, sc2 = st.columns(2)

            with sc1:
                st.markdown(
                    """
                    ### Helpful Tips & Information
                    - For adults the target to reach for sleep is 7-9 hours per 24 hours.
                    - Sleep quality is just as important as the quantity.
                    - Try to avoid interruptions.
                    """
                )
                control_header = st.empty()
                fig = self.display_day_sleep()
                if fig is not None:
                    control_header.subheader('Date controls')

            with sc2:
                if fig is None:
                    st.info('You have not reported any sleep information.')
                else:
                    st.plotly_chart(fig)

        with weight:
            fig = self.display_user_weight()

            c1, c2 = st.columns(2)
            with c2:
                if fig is None:
                    st.info('You have not reported any weight or BMI information.')
                else:
                    st.plotly_chart(fig)
            with c1:
                st.markdown(
                    """
                    ### Helpful Tips & Information
                    Your goal should be to aim for the green area with your BMI.
    
                    The BMI plot is an indicator for where you stand in terms of weight.
                    - Underweight: <18.5 
                    - Normal: 18.5 - 24.9
                    - Overweight: 25.0-29.9 
                    - Obese: >30.0
                    """
                )

    def main_window(self):
        bar = px.bar(self.data, x='startDate', y='sleepDuration')
        st.plotly_chart(bar)
        self.data['date'] = pd.to_datetime(self.data['startDate']).dt.date
        self.data = self.data[
            (self.data['sleepDuration'] > 4) & (self.data['date'] > pd.to_datetime('2020-01-01').date())]
        line = px.line(self.data, x='date', y='sleepDuration')
        st.plotly_chart(line)


app = App()
app.run()
