import os
import sys
import getpass
import warnings

import re

import pandas as pd
import numpy as np

from tqdm import tqdm
import datetime

from .sqlhelper import _get_config, _get_credentials, _define_connector
from .sqlhelper import _insert_data, _cache
from .data import write, f_read
from .format import file_age, verbose_display


#######################################################################################################################

# TODO: Test send_email by list of recipients
# TODO: remote_execute_sql with dump to S3 or to S3 and Redshift.

#######################################################################################################################

# Publish or read from DB
def remote_execute_sql(sql_query="", query_type="", table="", data={}, credentials={}, verbose=True, autofill_nan=True, useIAM=False,
                       engine='default', cache=False, cache_name=None, **kwargs):
    """Simplified function for executing SQL queries. Will look at the credentials at :obj:`/etc/config.json`. User can also pass a dictionnary for
    credentials.

    :Parameters:
        * **sql_query** (:obj:`str`): SQL query to be executed. Allows a string containing the SQL or a path containing the extension '.sql' (defaults "").
        * **query_type** (:obj:`str`): Type of SQL query to execute. Can either be SELECT, INSERT, COPY, DELETE or UNLOAD (defaults "SELECT").
        * **table** (:obj:`str`): Table in which we want to operate, only used for INSERT and DELETE (defaults "").
        * **data** (:obj:`pandas.DataFrame`): Data to load on the database (defaults {}).
        * **credentials** (:obj:`dict`): Credentials to use to connect to the database. You can also provide the credentials path or the json file name from '/etc/' (defaults {}).
        * **verbose** (:obj:`bool`): Display progression bar (defaults True).
        * **autofill_nan** (:obj:`bool`): Replace NaN values by 'NULL' (defaults True).
        * **useIAM** (:obj:`bool`): Get AWS IAM credentials using access and secret key (defaults False).
        * **cache** (:obj:`str`): Caches the data to avoid running again the same SQL query (defaults False). Provide a :obj:`str` for the cache time.
        * **cache_name** (:obj:`str`): File name for storing cache data, if None the name will be generated by hashing the SQL (defaults None).
        * **\\*\\*kwargs** (:obj:`str`): Arguments to be passed to the :py:meth:`pycof.data.f_read` function.

    :Example:
        >>> pycof.remote_execute_sql("SELECT * FROM SCHEMA.TABLE LIMIT 10")

    :Returns:
        * :obj:`pandas.DataFrame`: Result of an SQL query if :obj:`query_type = "SELECT"`.
    """

    # ============================================================================================
    # Define the SQL type
    all_query_types = ['SELECT', 'INSERT', 'DELETE', 'COPY', 'UNLOAD', 'UPDATE', 'CREATE', 'GRANT']

    if (query_type != ""):
        # Use user input if query_type is not as its default value
        sql_type = query_type
    elif type(data) == pd.DataFrame:
        # If data is provided, use INSERT sql_type
        sql_type = 'INSERT'
    elif type(sql_query) == pd.DataFrame:
        # If data is instead of an SQL query, use INSERT sql_type
        sql_type = 'INSERT'
        data = sql_query
    elif ("UNLOAD " in sql_query.upper()):
        sql_type = 'UNLOAD'
    elif ("COPY " in sql_query.upper()):
        sql_type = 'COPY'
    elif ("UPDATE " in sql_query.upper()):
        sql_type = 'UPDATE'
    elif (sql_query != ""):
        # If a query is inserted, use select.
        # For DELETE or COPY, user needs to provide the query_type
        sql_type = "SELECT"
    else:
        allowed_queries = f"Your query_type value is not correct, allowed values are {', '.join(all_query_types)}"
        # Check if the query_type value is correct
        raise ValueError(allowed_queries + f'. Got {query_type}')
        # assert query_type.upper() in all_query_types, allowed_queries

    # ============================================================================================
    # Process SQL query
    if sql_type != 'INSERT':
        if (sql_query != "") & ('.sql' in sql_query.lower()):
            # Can read an external file is path is given as sql_query
            sql_query = f_read(sql_query, extension='sql', **kwargs)
            assert sql_query != '', 'Could not read your SQL file properly. Please make sure your file is saved or check your path.'

    # ============================================================================================
    # Credentials load
    hostname, port, user, password, database = _get_credentials(_get_config(credentials), useIAM)

    # ============================================================================================
    # Set default value for table
    if (sql_type == 'SELECT'):  # SELECT
        if (table == ""):  # If the table is not specified, we get it from the SQL query
            table = sql_query.upper().replace('\n', ' ').split('FROM ')[1].split(' ')[0]
        elif (sql_type == 'SELECT') & (table.upper() in sql_query.upper()):
            table = table
        else:
            raise SyntaxError('Argument table does not match with SQL statement')

    # ============================================================================================
    # Database connector
    conn, cur = _define_connector(hostname, port, user, password, database, engine=engine)

    # ========================================================================================
    # SELECT - Read query
    if sql_type.upper() == "SELECT":
        if cache:
            read = _cache(sql_query, conn, sql_type, cache_time=cache, verbose=verbose, cache_file_name=cache_name)
        else:
            read = pd.read_sql(sql_query, conn, coerce_float=False)
        return(read)
    # ============================================================================================
    # INSERT - Load data to the db
    elif sql_type.upper() == "INSERT":
        _insert_data(data, table, conn, cur, autofill_nan, verbose)

    # ============================================================================================
    # DELETE / COPY / UNLOAD - Execute SQL command which does not return output
    elif sql_type.upper() in ["CREATE", "GRANT", "DELETE", "COPY", "UNLOAD", "UPDATE"]:
        if table.upper() in sql_query.upper():
            cur.execute(sql_query)
            conn.commit()
        else:
            raise ValueError('Table does not match with SQL query')
    else:
        raise ValueError(f'Unknown query_type, should be as: {all_query_types}')

    # Close SQL connection
    conn.close()

#######################################################################################################################
