"""
    Returns gdrive connection. Note, if no token.pickle or credentials.json
    files are found, OAuth2 flow is promoted to be completed via UI.
"""
import csv
import io
import os
import re
import os.path
import json
import pandas as pd

from hellofresh_data import logging_setup
from hellofresh_data.parameter_store import get_parameter_store_value

from googleapiclient.http import MediaIoBaseDownload
from googleapiclient.errors import HttpError
from googleapiclient.discovery import build
from google.oauth2.service_account import Credentials

PATH_TO_JSON = '/US/OA/Prod/GoogleAPI/service_account_json'

class GoogleDrive():
    """
        Google Drive Helper Class
    """
    def __init__(self):

        self.__location__ = os.getcwd()

        self._logger = logging_setup.init_logging("GoogleDrive")

        self.number_of_rows_pulled = 0
        self.file_name = None

    def get_google_api_service_account(self):
        """
            Returns Google API service account json as string.
        """
        service_account = get_parameter_store_value(PATH_TO_JSON)

        return service_account

    def get_gdrive_connection(self):
        """
            Uses service account json file, pulled from parameter store, to
            authenticate login. No browser login necessary.
        """
        scopes = ['https://www.googleapis.com/auth/drive']

        creds = None

        service_account_str = self.get_google_api_service_account()

        creds = json.loads(service_account_str)
        with open(os.path.join(self.__location__, 'service_account.json'), 'w') as fp:
            json.dump(creds, fp)

        service_account_file = \
        os.path.join(self.__location__, 'service_account.json')

        creds = Credentials.from_service_account_file(service_account_file,
                                                      scopes=scopes)

        return build('drive', 'v3', credentials=creds)

    def get_gsheet_connection(self):
        """
            Uses service account json file, pulled from parameter store, to
            authenticate login. No browser login necessary.
        """
        scopes = ['https://www.googleapis.com/auth/spreadsheets.readonly']

        creds = None

        service_account_str = self.get_google_api_service_account()

        creds = json.loads(service_account_str)
        with open(os.path.join(self.__location__, 'service_account.json'), 'w') as fp:
            json.dump(creds, fp)

        service_account_file = \
        os.path.join(self.__location__, 'service_account.json')

        creds = Credentials.from_service_account_file(service_account_file,
                                                      scopes=scopes)

        return build('sheets', 'v4', credentials=creds)

    def get_gsheet_file_by_id(self, sheet_key, sheet_range):
        """
            Get google sheet data by spreadsheet id
        """
        service = self.get_gsheet_connection()

        sheet = service.spreadsheets()

        try:
            result = sheet.values().get(spreadsheetId=sheet_key,
                                        range=sheet_range).execute()
        except HttpError as err:
            self._logger.warning('No files or sheet found with id: %s',
                                 sheet_key)

        values = result.get('values', [])

        return values

    def camel_to_snake(self, column_name):
        return re.sub('(.)([A-Z][a-z]+)', r'\1_\2', column_name)

    def get_gsheet_file_as_df(self, sheet_key, sheet_range, sheet_header_row,
                              clean_col_name_flag=False):
        """
        Returns sheet id for a given range as a pandas df

        Parameters
        ----------
        sheet_key : str
            id of the google sheet
        sheet_range : str
            name of tab and sheet_range to pull in the following format:
            name_of_the_tab!sheet_rangeFrom:sheet_rangeTo
                i.e. forecast_recipes!A1:G
        sheet_header_row : int
            Integer representation of the header row.
            If very first column of the google sheet is the header
            sheet_header_row=0
        clean_col_name_flag : bool
            If set to True will turn columns into snake case and replace any
            non alphanumetic characters with an underscore:
                "This Is a $Col" -> this_is_a_col

        Returns
        -------
        pandas df
            Pandas representation of google sheet
        """

        data = self.get_gsheet_file_by_id(sheet_key, sheet_range)

        try:
            data_df = pd.DataFrame(data)
        except Exception as err:
            self._logger.error(err)

        new_header = data_df.iloc[sheet_header_row]
        data_df = data_df[sheet_header_row+1:]
        data_df.columns = new_header

        if clean_col_name_flag:
            data_df.columns = [self.camel_to_snake(x) for x in data_df.columns]
            data_df.columns = ([re.sub('[^0-9a-zA-Z]+', '_',
                                       x.lower()) for x in data_df.columns])
            data_df.columns = ([x.replace('__', '_') for x in data_df.columns])
            data_df.columns = ([x.strip('_') for x in data_df.columns])

        return data_df

    def get_gdrive_csv_by_name(self, file_name):
        """
              Search drive by file name and check if file is present.
        """
        self.file_name = file_name
        gdrive_service = self.get_gdrive_connection()

        response = \
        gdrive_service.files().list(q="name='{}'".format(self.file_name),
                                    spaces='drive',
                                    fields='nextPageToken, files(id, name)'
                                    ).execute()

        response_obj = response.get('files', [])

        if not response_obj:
            self._logger.warning('No files found with name: %s', self.file_name)
        else:
            self._logger.info('Found file: %s', response_obj[0])

        data_io = self.get_gdrive_csv_by_id(response_obj[0].get('id'))

        return data_io

    def get_gdrive_csv_by_id(self, sheet_key):
        """
            Search drive by file ID and check if file is present.
            If present, download.
        """
        gdrive_service = self.get_gdrive_connection()

        try:
            response = gdrive_service.files().get_media(fileId=sheet_key)
            fh_io = io.BytesIO()
            downloader = MediaIoBaseDownload(fh_io, response, chunksize=1024*1024)

            self._logger.info('Downloading "%s" from drive...', self.file_name)
            done = False
            while done is False:
                status, done = downloader.next_chunk(num_retries=2)

        except HttpError as err:
            self._logger.error(err)

        self._logger.info('Downloaded "%s" successfully!', self.file_name)

        return fh_io.getvalue()

    def convert_drive_io_data_to_df(self, data):
        """
            Get the data streamed from google drive and convert
            to DataFrame.
        """

        self._logger.info('Convert stream drive data to pandas DataFrame')

        decoded_data = data.decode('utf-8')
        file_io = io.StringIO(decoded_data)
        reader = csv.reader(file_io, delimiter=',')

        data_df = pd.DataFrame(reader)
        data_df = data_df.infer_objects()

        self.number_of_rows_pulled = len(data_df)

        self._logger.info('Pulled %s rows from drive file', self.number_of_rows_pulled)

        return data_df
