"""
    Reader class for DWH

    Default connection is using sca service account's credentials
"""
import sys
import os
import yaml
import pandas as pd
from impala.dbapi import connect
from hellofresh_data import logging_setup
from hellofresh_data.parameter_store import get_parameter_store_value


class ImpalaReader():

    def __init__(self, user=None, password=None):
        self.__user_var = user
        self.__password_var = password
        self._logger = logging_setup.init_logging('ImpalaReader')
        self.instantiate_config()
        self.instantiate_impala_creds()
        self.__get_connection()

    def __del__(self):
        try:
            self.__impala_conn.close()
        except Exception:
            self.__impala_conn = None

    def instantiate_config(self):
        """
            Instantiate config to expose variables
        """
        self.__location__ = \
            os.path.realpath(
                os.path.join(os.getcwd(), os.path.dirname(__file__)))
        self.config = \
            yaml.safe_load(open(os.path.join(self.__location__, 'config.yml')))

    def instantiate_impala_creds(self):
        """
            Set DWH variables

            For self.__user and self.__password either use passed in variables
            or assign those of a sca service account
        """
        __user = self.config['impala_reader']['dwh']['user']
        __password = self.config['impala_reader']['dwh']['password']

        self.__user = self.__user_var or get_parameter_store_value(__user)
        self.__password = \
            self.__password_var or get_parameter_store_value(__password)

        self._logger.info(f'Using creds for user: {self.__user}')

        self.host = self.config['impala_reader']['dwh']['host']
        self.port = self.config['impala_reader']['dwh']['port']
        self.auth_mechanism = \
            self.config['impala_reader']['dwh']['auth_mechanism']
        self.use_ssl = self.config['impala_reader']['dwh']['use_ssl']

    def __get_connection(self):
        """
            Try to establish connection to DWH using set variables
        """

        try:
            self.__impala_conn = connect(host=self.host,
                                         port=self.port,
                                         user=self.__user,
                                         password=self.__password,
                                         auth_mechanism=self.auth_mechanism,
                                         use_ssl=self.use_ssl)
        except Exception as err:
            self._logger.error('Failed to connect to DWH')
            self._logger.error(err)

    def validate_sql(self, sql):
        """
            Run basic checks on SQL.
            1. First command of SQL must be in the "allowed_command_ls" list
            2. Commands in forbidden_command_ls cannot exist in SQL
            3. Only one statement can be executed at a time

            This is more of a foolproof check vs. checking for an
            actual malicious behaviour. This "ensures" we only use this
            class for reading/refreshing data.
        """
        sql_parts = sql.strip().split(' ')
        delimiter_count = sql.strip().count(';')

        allowed_command_ls = \
            self.config['impala_reader']['sql_validate']['allowed_command']

        if sql_parts[0].lower() not in allowed_command_ls:
            self._logger.error(f'Can only run: {allowed_command_ls}')
            sys.exit(1)

        forbidden_command_ls = \
            self.config['impala_reader']['sql_validate']['forbidden_command']

        if any(ext in sql_parts for ext in forbidden_command_ls):
            self._logger.error(f'SQL cannot include: {forbidden_command_ls}')
            sys.exit(1)

        try:
            delimiter_count = int(delimiter_count)
        except Exception:
            delimiter_count = 0
            pass

        if int(delimiter_count) > 1:
            self._logger.error('Can only execute 1 sql command at a time')
            sys.exit(1)

    def execute_sql(self, sql):
        """
            Validate passed in query and then try to execute without return
            value. This function is meant to run "refresh"
            and "invalidate metadata" statements.
        """
        self.validate_sql(sql)

        cursor = self.__impala_conn.cursor()
        try:
            cursor.execute(sql)
            self._logger.info(f'SUCCESS: {sql}')
        except Exception as err:
            self._logger.error(err)
            sys.exit(1)

    def get_result_of_sql_as_df(self, sql):
        """
            Validate passed in query and then try to execute and return as a
            pandas DataFrame. This function is meant to run "select"
            statements.
        """
        self.validate_sql(sql)

        try:
            df = pd.read_sql(sql, self.__impala_conn)
        except Exception as err:
            df = None
            self._logger.error(err)

        return df
