import os
from datetime import datetime

from scripts.constants.app_configuration import KAIROS_DB_HOST, METADATA
from scripts.core.data.data_import import DataPuller
from loguru import logger
import pandas as pd


class Compliance:
    def __init__(self, site, column_rename, tags_data, mandatory_cols):
        self.site = site
        self.payload = METADATA['query']
        self.column_rename = column_rename
        self.tags_data = tags_data
        self.site_metadata = METADATA['site_data'][self.site]
        self.delta_tag_data = self.site_metadata['delta_data']
        self.delta_column_name = list(self.delta_tag_data.keys())[0]
        self.delta_column_tag = self.delta_tag_data[self.delta_column_name]['delta']
        self.mandatory_cols = mandatory_cols
        self.mandatory_cols.append(self.delta_column_name)
        self.column_rename.update({self.delta_column_tag: self.delta_column_name})
        self.opportunity_uom = self.site_metadata['uom']
        self.payload['metrics'][0]['tags']['c3'] = list(self.column_rename.keys())
        self._dp_ = DataPuller(db_host=KAIROS_DB_HOST, payload=self.payload, column_rename=self.column_rename)
        self.data_path = 'delta-data'
        self.data_path = os.path.join(self.data_path, self.site)
        if not os.path.exists(self.data_path):
            os.makedirs(self.data_path)

    @staticmethod
    def add_compliance_column(df, column, live_col, upper_col, lower_col):
        try:
            df[f'{column}_compliance'] = (df[live_col] > df[lower_col]) & (df[live_col] < df[upper_col])
        except Exception as e:
            logger.warning(f"Error adding compliance column - {e}")
        return df

    def create_compliance_sheet(self, final_data_dict, compliance_cols, total_columns_criteria):
        logger.info("Calculating overall compliance...")
        c_df = pd.DataFrame()
        try:
            c_df['client_name'] = [self.site_metadata['client']] * len(final_data_dict)
            c_df['site_name'] = [self.site_metadata['site']] * len(final_data_dict)
            c_df['project_name'] = [self.site_metadata['project_name']] * len(final_data_dict)
            final_compliance_list = []
            timestamp_data = []
            delta_data = []
            for idx, value in final_data_dict.items():
                data_list = []
                for k, v in value.items():
                    if k in compliance_cols:
                        data_list.append(v)
                    elif k in ['timestamp']:
                        timestamp_data.append(v)
                    else:
                        delta_data.append(round(v, 2))
                count = data_list.count(True)
                logger.info(f"Total followed recommendations were {count} and required recommendations were "
                            f"{total_columns_criteria}/{len(compliance_cols)}")
                if count >= total_columns_criteria:
                    final_compliance_list.append(1)
                else:
                    final_compliance_list.append(0)
            c_df['time'] = timestamp_data
            c_df['compliance'] = final_compliance_list
            delta_data_list = [round(i, 2) for i in delta_data]
            c_df['total_opportunity'] = delta_data_list
        except Exception as e:
            logger.warning(f'Error - {e}')
        return c_df

    @staticmethod
    def calculate_opportunity_or_loss(df):
        opportunity_df = df.groupby(['day', 'compliance'])['total_opportunity'].sum().reset_index()
        dates_list = sorted(list(set(df['day'])))
        final_df = pd.DataFrame()
        final_dates = []
        capitalised_opportunity = []
        opportunity_lost = []
        for dt in dates_list:
            final_dates.append(dt)
            temp_df = opportunity_df[opportunity_df['day'] == dt]
            gain_df = temp_df[temp_df['compliance'] == 1]
            loss_df = temp_df[temp_df['compliance'] == 0]
            if len(gain_df) > 0:
                gain_value = round(list(gain_df['total_opportunity'])[0], 2)
                logger.info(f"For day {str(dt)} there is capitalised opportunity of {gain_value}")
            else:
                gain_value = 0
                logger.info(f"For day {str(dt)} no capitalised opportunity")
            capitalised_opportunity.append(gain_value)
            if len(loss_df) > 0:
                loss_value = round(list(loss_df['total_opportunity'])[0], 2)
                logger.info(f"For day {str(dt)} there is opportunity lost of {loss_value}")
            else:
                loss_value = 0
                logger.info(f"For day {str(dt)} there is no opportunity lost")
            opportunity_lost.append(loss_value)
        final_df['day'] = final_dates
        final_df['capitalised_opportunity'] = capitalised_opportunity
        final_df['opportunity_lost'] = opportunity_lost
        return final_df

    def generate_day_wise(self, final_df):
        final_df['time'] = pd.to_datetime(final_df['time'])
        final_df['day'] = final_df['time'].dt.date
        delta_df = final_df.groupby('day')['total_opportunity'].sum().reset_index()
        delta_df['total_opportunity'] = round(delta_df['total_opportunity'], 2)
        opp_df = self.calculate_opportunity_or_loss(final_df)
        new_df = final_df.groupby('day')['compliance'].value_counts().unstack(fill_value=0).reset_index()
        new_df['event_date'] = pd.to_datetime(new_df['day'])
        if 0 not in list(new_df.columns):
            new_df[0] = 0
        if 1 not in list(new_df.columns):
            new_df[1] = 0
        new_df = new_df.rename(columns={0: 'not_followed', 1: 'followed'})
        new_df['client_name'] = self.site_metadata['client']
        new_df['site_name'] = self.site_metadata['site']
        new_df['project_name'] = self.site_metadata['project_name']
        new_df['location'] = self.site_metadata['location']
        new_df['total_recommendations'] = new_df['followed'] + new_df['not_followed']
        new_df['compliance_percentage'] = round((new_df['followed'] * 100) / new_df['total_recommendations'], 2)
        temp_df = pd.merge(new_df, delta_df, left_on='day', right_on='day', how='left')
        total_df = pd.merge(temp_df, opp_df, left_on='day', right_on='day', how='left')
        total_df['capitalisation_percentage'] = \
            round((100 * total_df['capitalised_opportunity']) / total_df['total_opportunity'], 2)
        total_df['opportunity_uom'] = self.opportunity_uom
        total_df = total_df[['client_name', 'site_name', 'location', 'project_name', 'event_date',
                             'total_recommendations', 'followed', 'not_followed', 'compliance_percentage',
                             'total_opportunity', 'capitalisation_percentage', 'capitalised_opportunity',
                             'opportunity_lost', 'opportunity_uom']]
        total_df.fillna(0, inplace=True)
        return total_df

    def get_compliance(self, compliance_cols, df, start_time, end_time):
        total_columns_criteria = \
            int((self.site_metadata['compliance_percentage'] / 100) * len(compliance_cols))
        logger.info(f"Need {total_columns_criteria} from {len(compliance_cols)} columns to satisfy the "
                    f"compliance")
        rq_cols = compliance_cols.copy()
        rq_cols.append('timestamp')
        rq_cols.append(self.delta_column_name)
        df = df[rq_cols]
        df.to_csv(os.path.join(self.data_path, f'{self.site}-{str(start_time.date())}-'
                                               f'{str(end_time.date())}-col-compliance.csv'), index=False)
        final_data_dict = df.to_dict(orient='index')

        df_final = self.create_compliance_sheet(final_data_dict, compliance_cols, total_columns_criteria)

        return df_final

    def add_mandatory_cols(self, df, start_time, end_time):
        for c in self.mandatory_cols:
            if c not in list(df.columns):
                logger.warning(
                    f"for {self.site} within {start_time} and {end_time} there is no - {c} so adding 0")
                df[c] = 0
        return df

    def start_calculation(self, all_timestamps):
        all_dfs = []
        parameter_wise_dfs = []
        for i in all_timestamps:
            start_timestamp = i['start']
            end_timestamp = i['end']
            start_time = datetime.fromtimestamp(start_timestamp // 1000)
            end_time = datetime.fromtimestamp(end_timestamp // 1000)
            logger.info(f"Calculating for {start_time} to {end_time}")
            df = self._dp_.get_data(start_timestamp, end_timestamp)
            df.to_csv(os.path.join(self.data_path, f"{self.site}-{str(start_time.date())}-{str(end_time.date())}.csv"),
                      index=False)
            df = self.add_mandatory_cols(df, start_time, end_time)
            total_cols = len(df.columns) - 2
            required_total_cols = len(self.tags_data) * 3 + 1
            if total_cols != required_total_cols:
                logger.warning(f"No Data for {start_time} to {end_time}")
            else:
                # df.fillna(0, inplace=True)
                df[self.delta_column_name] = df[self.delta_column_name].fillna(method='ffill')
                df.dropna(inplace=True)
                if len(df) > 0:
                    compliance_cols = []
                    for column, column_data in self.tags_data.items():
                        df = self.add_compliance_column(df, column, f'{column}_live', f'{column}_upper',
                                                        f'{column}_lower')
                        compliance_cols.append(f'{column}_compliance')
                    parameter_wise_dfs.append(df)
                    all_dfs.append(self.get_compliance(compliance_cols, df, start_time, end_time))
                else:
                    logger.info(f"There is no data to calculate compliance between {str(start_time.date())} to "
                                f"{str(end_time.date())}")
        if all_dfs:
            logger.info("Combining the Data")
            final_df = pd.concat(all_dfs)
            new_df = self.generate_day_wise(final_df)
            new_df.to_csv(os.path.join(self.data_path, f'{self.site}-day-wise-compliance.csv'), index=False)
            final_df.to_csv(os.path.join(self.data_path, f'{self.site}-overall-compliance.csv'), index=False)
            return new_df
        return pd.DataFrame()
