import inspect
import logging
import datetime as dt
from datetime import timedelta, datetime
from sqlalchemy.sql.sqltypes import TIMESTAMP,VARCHAR
import numpy as np
import pandas as pd

from iotfunctions.base import BaseTransformer
from iotfunctions import ui

logger = logging.getLogger(__name__)

# Specify the URL to your package here.
# This URL must be accessible via pip install

PACKAGE_URL = 'git+https://github.com/rweidinger/functions@'


class PrevDayHourlyAvgDiff(BaseTransformer):
    '''
    Calculate difference between current data item value and the mean of a data item from yesterday during the same hour
    as current hour
    '''

    execute_by = ['deviceid']

    def __init__(self,input_item,output_item='PrevDayHourlyAvgDiff'):
        self.input_item = input_item
        self.output_item = output_item
        super().__init__()

    def _calc(self,df):
        df = df.copy()
        today = dt.datetime.today()
        yesterday = today - dt.timedelta(days=1)
        yesterday_date = yesterday.date()
        yesterday_hour = yesterday.hour
        yesterday_values_hour = df.loc[(df['_timestamp'].dt.date == yesterday_date) &
                                       (df['_timestamp'].dt.hour == yesterday_hour)][self.input_item]
        df[self.output_item] = df[self.input_item] - yesterday_values_hour.mean()
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate average of same hour of previous day'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='PrevDayHourlyAvgDiff',
            datatype=float,
            description='PrevDayHourlyAvgDiff'
        ))
        return inputs,outputs


class PrevDayHourlyAvgDiffMultiOut(BaseTransformer):
    '''
    Calculate difference between current data item value and the mean of a data item from yesterday during the same hour
    as current hour, providing both sensor reading and calculation
    '''

    execute_by = ['deviceid']

    def __init__(self,input_item,output_item1='output_item1',output_item2='output_item2'):
        self.input_item = input_item
        self.output_item1 = output_item1
        self.output_item2 = output_item2
        super().__init__()

    def _calc(self,df):
        df = df.copy()
        today = dt.datetime.today()
        yesterday = today - dt.timedelta(days=1)
        yesterday_date = yesterday.date()
        yesterday_hour = yesterday.hour
        yesterday_values_hour = df.loc[(df['_timestamp'].dt.date == yesterday_date) &
                                       (df['_timestamp'].dt.hour == yesterday_hour)][self.input_item]
        yesterday_values_hour_mean = yesterday_values_hour.mean()
        df[self.output_item1] = df[self.input_item]
        df[self.output_item2] = df[self.input_item] - yesterday_values_hour_mean
        return df

    @classmethod
    def build_ui(cls):
        # Define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate average of same hour of previous day'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item1',
            datatype=float,
            description='output_item1',
        ))
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item2',
            datatype=float,
            description='output_item2',
        ))

        return inputs,outputs


class PrevDayHourlyAvgPercentDiff(BaseTransformer):
    '''
    Calculate percent difference between current data item value and the mean of a data item from yesterday during the
    same hour as current hour
    '''

    execute_by = ['deviceid']

    def __init__(self,input_item,output_item='PrevDayHourlyAvgPercDiff'):
        self.input_item = input_item
        self.output_item = output_item
        super().__init__()

    def _calc(self,df):
        df = df.copy()
        today = dt.datetime.today()
        yesterday = today - dt.timedelta(days=1)
        yesterday_date = yesterday.date()
        yesterday_hour = yesterday.hour
        yesterday_values_hour = df.loc[(df['_timestamp'].dt.date == yesterday_date) &
                                       (df['_timestamp'].dt.hour == yesterday_hour)][self.input_item]
        yesterday_values_hour_mean = yesterday_values_hour.mean()
        df[self.output_item] = ((df[self.input_item] - yesterday_values_hour_mean) / abs(
            yesterday_values_hour_mean)) * 100
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate average of same hour of previous day'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='PrevDayHourlyAvgPercDiff',
            datatype=float,
            description='PrevDayHourlyAvgPercDiff'
        ))
        return inputs, outputs

class PrevDayHourlyAvgPercentDiffMultiOut(BaseTransformer):
    '''
    Calculate percent difference between current data item value and the mean of a data item from yesterday during the
    same hour as current hour, returning input item and calculation
    '''

    execute_by = ['deviceid']

    def __init__(self, input_item, output_item1='output_item1', output_item2='output_item2'):
        self.input_item = input_item
        self.output_item1 = output_item1
        self.output_item2 = output_item2
        super().__init__()

    def _calc(self, df):
        df = df.copy()
        today = dt.datetime.today()
        yesterday = today - dt.timedelta(days=1)
        yesterday_date = yesterday.date()
        yesterday_hour = yesterday.hour
        yesterday_values_hour = df.loc[(df['_timestamp'].dt.date == yesterday_date) &
                                       (df['_timestamp'].dt.hour == yesterday_hour)][self.input_item]
        yesterday_values_hour_mean = yesterday_values_hour.mean()
        df[self.output_item1] = df[self.input_item]
        df[self.output_item2] = ((df[self.input_item] - yesterday_values_hour_mean) / abs(yesterday_values_hour_mean)) \
                                * 100
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate average of same hour of previous day'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item1',
            datatype=float,
            description='output_item1',
        ))
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item2',
            datatype=float,
            description='output_item2',
        ))
        return inputs, outputs


class PrevDayDailyAvgPercentDiff(BaseTransformer):
    '''
    Calculate percent difference between current data item value and the mean of a data item from previous 24 hours
    '''

    execute_by = ['deviceid']

    def __init__(self,input_item,output_item='PrevDayDailyAvgPercDiff'):
        self.input_item = input_item
        self.output_item = output_item
        super().__init__()

    def _calc(self,df):
        df = df.copy()
        today = dt.datetime.today()
        yesterday = today - dt.timedelta(days=1)
        yesterday_values = df.loc[(df['_timestamp'].dt.date == yesterday.date())][self.input_item]
        yesterday_values_mean = yesterday_values.mean()
        df[self.output_item] = ((df[self.input_item] - yesterday_values_mean) / abs(yesterday_values_mean)) * 100
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate average of same hour of previous day'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='PrevDayDailyAvgPercDiff',
            datatype=float,
            description='PrevDayDailyAvgPercDiff'
        ))
        return inputs,outputs


class PrevHourAvgDiff(BaseTransformer):
    '''
    Calculate difference between current data item value and the mean of a data item from the previous hour
    '''

    execute_by = ['deviceid']

    def __init__(self,input_item, output_item):
        self.input_item = input_item
        self.output_item = output_item
        super().__init__()

    def _calc(self,df):
        df = df.copy()
        today = dt.datetime.today()
        prev_hour = today - timedelta(hours=1)
        prev_hour_values = df.loc[df['_timestamp'] >= prev_hour][self.input_item]
        df[self.output_item] = df[self.input_item] - prev_hour_values.mean()
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate average of same hour of previous day'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item',
            datatype=float,
            description='PrevHourAvgDiff'
        ))
        return inputs, outputs

class PrevDayDailyAvgPercentDiffMultiOut(BaseTransformer):
   '''
   Calculate percent difference between current data item value and the mean of a data item from previous 24 hours,
   returning the current data item and the percent change calculation
   '''

   execute_by = ['deviceid']

   def __init__(self,input_item,output_item1='output_item1', output_item2='output_item2'):
       self.input_item = input_item
       self.output_item1 = output_item1
       self.output_item2 = output_item2
       super().__init__()

   def _calc(self, df):
       df = df.copy()
       today = dt.datetime.today()
       yesterday = today - dt.timedelta(days=1)
       yesterday_values = df.loc[(df['_timestamp'].dt.date == yesterday.date())][self.input_item]
       yesterday_values_mean = yesterday_values.mean()
       df[self.output_item1] = df[self.input_item]
       df[self.output_item2] = ((df[self.input_item] - yesterday_values_mean) / abs(yesterday_values_mean)) * 100
       return df

   @classmethod
   def build_ui(cls):
       # define arguments that behave as function inputs
       inputs = []
       inputs.append(ui.UISingleItem(
           name='input_item',
           datatype=float,
           description='Data item to calculate average of same hour of previous day'
       ))
       # define arguments that behave as function outputs
       outputs = []
       outputs.append(ui.UIFunctionOutSingle(
           name='output_item1',
           datatype=float,
           description='output_item1',
       ))
       outputs.append(ui.UIFunctionOutSingle(
           name='output_item2',
           datatype=float,
           description='output_item2',
       ))
       return inputs, outputs

class PrevHourDiff(BaseTransformer):
    '''
    Calculates the difference of a data item's value from an hour ago and the current value
    '''

    execute_by = ['deviceid']

    def __init__(self, input_item, output_item):
        self.input_item = input_item
        self.output_item = output_item
        super().__init__()

    def _calc(self,df):
        df = df.copy()
        today = dt.datetime.today()
        prev_hour = today - timedelta(hours=1)
        prev_hour_top_window = prev_hour + timedelta(minutes=16)
        prev_hour_bot_window = prev_hour - timedelta(minutes=16)
        prev_hour_values = df.loc[(df['_timestamp'] >= prev_hour_bot_window) &
                                 (df['_timestamp'] <= prev_hour_top_window)][self.input_item]
        prev_hour_values_mean = prev_hour_values.mean()
        df[self.output_item] = df[self.input_item] - prev_hour_values_mean
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate average of same hour of previous day'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item',
            datatype=float,
            description='PrevHourDiff'
        ))
        return inputs, outputs

class PrevDayDiff(BaseTransformer):
    '''
    Calculates the difference of a data item's value from 24 hours ago and the current value
    '''

    execute_by = ['deviceid']

    def __init__(self, input_item, output_item):
        self.input_item = input_item
        self.output_item = output_item
        super().__init__()

    def _calc(self, df):
        df = df.copy()
        today = dt.datetime.today()
        prev_day = today - timedelta(days=1)
        prev_day_top_window = prev_day + timedelta(minutes=16)
        prev_day_bot_window = prev_day - timedelta(minutes=16)
        prev_day_values = df.loc[(df['_timestamp'] >= prev_day_bot_window) &
                                 (df['_timestamp'] <= prev_day_top_window)][self.input_item]
        prev_day_values_mean = prev_day_values.mean()
        df[self.output_item] = df[self.input_item] - prev_day_values_mean
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate average of same hour of previous day'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item',
            datatype=float,
            description='PrevDayDiff'
        ))
        return inputs, outputs


class HistDayDiffTotalEntity(BaseTransformer):
    '''
    Calculates the difference between max and min of a data item during each calendar day of the week, by entity
    '''

    execute_by = ['deviceid']

    def __init__(self, input_item, output_item):
        self.input_item = input_item
        self.output_item = output_item
        super().__init__()

    def _calc(self, df):
        df = df.copy()
        df['date'] = df['_timestamp'].dt.date
        date_min = df.groupby(['date']).agg('min')[self.input_item]
        date_max = df.groupby(['date']).agg('max')[self.input_item]
        date_usage = date_max - date_min
        date_usage_df = pd.DataFrame(date_usage).reset_index()
        date_usage_df.columns = ['date', 'difference']
        df = df.merge(date_usage_df, on='date', how='left')
        df[self.output_item] = df['difference']
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate daily sum'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item',
            datatype=float,
            description='HistDaySumEntity'
        ))
        return inputs, outputs


class HistMonthDiffTotalEntity(BaseTransformer):
    '''
    Calculates the difference between max and min of a data item during each calendar month, by entity
    '''

    execute_by = ['deviceid']

    def __init__(self, input_item, output_item):
        self.input_item = input_item
        self.output_item = output_item
        super().__init__()

    def _calc(self, df):
        df = df.copy()
        if 'difference' in df.columns:
            df = df.drop('difference', axis=1)
        df['month'] = df['_timestamp'].dt.month
        df['year'] = df['_timestamp'].dt.year
        month_max = df.groupby(['year', 'month']).agg('max')[self.input_item]
        month_min = df.groupby(['year', 'month']).agg('min')[self.input_item]
        month_usage = month_max - month_min
        month_usage_df = pd.DataFrame(month_usage).reset_index()
        month_usage_df.columns = ['year', 'month', 'difference']
        df = df.merge(month_usage_df, on=['year', 'month'], how='left')
        df[self.output_item] = df['difference']
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item',
            datatype=float,
            description='Data item to calculate daily sum'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item',
            datatype=float,
            description='HistDaySumEntity'
        ))
        return inputs, outputs


class HistDayDiffTotalDimAgg(BaseTransformer):
    '''
    Calculates the difference between max and min of a data item during each calendar day, by aggregated on a dimension
    '''

    execute_by = ['deviceid']

    def __init__(self, input_item1, input_item2, output_item):
        self.input_item1 = input_item1
        self.input_item2 = input_item2
        self.output_item = output_item
        super().__init__()

    def _calc(self, df):
        if 'difference' in df.columns:
            df = df.drop('difference', axis=1)
        df['date'] = df['_timestamp'].dt.date
        date_min = df.groupby(['date', self.input_item2]).agg('min')[self.input_item1]
        date_max = df.groupby(['date', self.input_item2]).agg('max')[self.input_item1]
        date_usage = date_max - date_min
        date_usage_df = pd.DataFrame(date_usage).reset_index()
        date_usage_df.columns = ['date',self.input_item2,'difference']
        df = df.merge(date_usage_df,on=['date', self.input_item2], how='left')
        df[self.output_item] = df['difference']
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item1',
            datatype=float,
            description='Data item to calculate daily sum'
        ))
        inputs.append(ui.UISingleItem(
            name='input_item2',
            datatype=str,
            description='Dimension to aggregate on'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item',
            datatype=float,
            description='HistDaySumEntity'
        ))
        return inputs, outputs


class HistMonthDiffTotalDimAgg(BaseTransformer):
    '''
    Calculates the difference between max and min of a data item during each calendar month, by aggregated on a dimension
    '''

    execute_by = ['deviceid']

    def __init__(self, input_item1, input_item2, output_item):
        self.input_item1 = input_item1
        self.input_item2 = input_item2
        self.output_item = output_item
        super().__init__()

    def _calc(self, df):
        df = df.copy()
        if 'difference' in df.columns:
            df = df.drop('difference', axis=1)
        df['month'] = df['_timestamp'].dt.month
        df['year'] = df['_timestamp'].dt.year
        month_max = df.groupby(['year', 'month', self.input_item2]).agg('max')[self.input_item1]
        month_min = df.groupby(['year', 'month', self.input_item2]).agg('min')[self.input_item1]
        month_usage = month_max - month_min
        month_usage_df = pd.DataFrame(month_usage).reset_index()
        month_usage_df.columns = ['year', 'month', self.input_item2, 'difference']
        df = df.merge(month_usage_df, on=['year', 'month', self.input_item2], how='left')
        df[self.output_item] = df['difference']
        return df

    @classmethod
    def build_ui(cls):
        # define arguments that behave as function inputs
        inputs = []
        inputs.append(ui.UISingleItem(
            name='input_item1',
            datatype=float,
            description='Data item to calculate daily sum'
        ))
        inputs.append(ui.UISingleItem(
            name='input_item2',
            datatype=str,
            description='Dimension to aggregate on'
        ))
        # define arguments that behave as function outputs
        outputs = []
        outputs.append(ui.UIFunctionOutSingle(
            name='output_item',
            datatype=float,
            description='HistDaySumEntity'
        ))
        return inputs, outputs
