import os
import json
import sys
import logging
import pandas as pd
from pyodbc import connect
from azure.storage.blob.blockblobservice import BlockBlobService

def get_azure_credentials(**kwargs):
    """ infers and/or updates defaults for Azure SQL and Azure storage account
    defaults comes from credentials.json filepath set as environment 'AZURE_CREDENTIALS'

    Keyword Arguments:
        account_name {str} 'https://{account_name}.blob.core.windows.net/{container_name}/{blob_name}'
        container_name {str} - storage container name
        account_key {str} - account_key for storage authentication

        server {str} -- azure sql server url e.g "myazuresql.database.windows.net"
        username {str} -- user id (default: admin )
        password {str} -- password (default: admin_password)
        database {str} -- database name (default: dev database)


    Returns:
    {dict} - credential dictionary

    """
    default_credpath =  os.environ.get('AZURE_CREDENTIALS')
    
    if default_credpath:
        with open(default_credpath, 'r') as file:
            credentials = json.load(file)
    else:
        logging.warning('default credentials path does not exit, no defaults set for tableau server authentication')
        credentials = {}
    
    if 'account_name' in kwargs:
        credentials['account_name'] = kwargs['account_name']
    
    if 'container_name' in kwargs:
        credentials['container_name'] = kwargs['container_name']

    if 'account_key' in kwargs:
        credentials['account_key'] = kwargs['account_key']


    if 'server' in kwargs:
        credentials['server'] = kwargs['server']
        
    if 'username' in kwargs:
        credentials['username'] = kwargs['username']
        
    if 'password' in kwargs:
        credentials['password'] = kwargs['password']
    
    if 'database' in kwargs:
        credentials['database'] = kwargs['database']
        
    return credentials

def download_blob_azure_storage(blob_url, account_key=None, filepath=None, output_option='FILE', delete_blob=False):
    """ downloads azure blob to FILE, io.BytesIO, STRING, JSON, google CREDENTIAL objects type


    Arguments:
        blob_url {str} -- azure storage blob url e.g https://account_name.blob.core.windows.net/container_name/blob_name
        account_key {str} -- authentication method for azure storage

    Keyword Arguments:
        filepath {str} -- folder_path/new_filename (default: current_working_directory/blob_name)
        output_option {str} -- determines function output type. (default: {'FILE'})
        delete_blob {bool} -- flag for deleting the blob after download is successful (default: {False})

    Returns:
        Returns:
        output_option : return type
        {STRING : unicode string}
        {IO : io.BytesIO}
        {URL: downloadable link}
        {CREDENTIALS: google.oauth2.service_account.Credentials}
        {JSON: DICT}
        {FILE: filepath} 
    """
    from io import BytesIO
    from google.oauth2.service_account import Credentials


    account_name, container_name, blob_name = __parse_storage_url(blob_url)
    if not account_key:
        account_key = get_azure_credentials().get('account_key')
        
    service = BlockBlobService(
        account_name=account_name, account_key=account_key)
    blob = service.get_blob_to_bytes(container_name, blob_name)

    if output_option == 'IO':
        output = BytesIO(blob.content)
        output.seek(0)

    elif output_option == 'STRING':
        output = blob.content.decode()
    
    elif output_option == 'JSON':
        output = json.loads(blob.content.decode())
    
    elif output_option == 'CREDENTIALS':
        output = Credentials.from_service_account_info(json.loads(blob.content.decode()))
    
    elif output_option == 'URL':
        from datetime import datetime, timedelta
        from azure.storage.blob import BlobPermissions
        from urllib.parse import quote

        sas_token = service.generate_blob_shared_access_signature(
            container_name=container_name
            ,blob_name=blob_name
            ,permission=BlobPermissions(read=True),
            expiry=datetime.utcnow() + timedelta(weeks=1)
        )
        output = service.make_blob_url(container_name, quote(blob_name),sas_token=sas_token)

    else:
        if not filepath:
            filepath = blob_name
        with open(filepath, mode='wb') as file:
            file.write(blob.content)
        output = filepath
    
    if delete_blob:
        service.delete_blob(container_name, blob_name)

    return output

def upload_blob_azure_storage(content, blob_name=None, **credentials):
    """Uploads content to Azure Storage

    Arguments:
        content {str, filepath, bytes, io.BytesIO} -- string, bytes, filepath or BytesIO to be uploaded


    Keyword Arguments:
        blob_name {str} -- blob name, for string and BytesIO name should be provided (default: {unname_blob_uploaded_at_timestamp})

    Returns:
        {str} -- returns Azure storage url of uploaded blob
    """
    import io
    from datetime import datetime
    credentials = get_azure_credentials(**credentials)
    
    account_name = credentials.get('account_name')
    container_name = credentials.get('container_name')
    account_key = credentials.get('account_key')
    
    service = BlockBlobService(
        account_name=account_name, account_key=account_key)
    
    if not blob_name:
        if os.path.isfile(content):
            blob_name= os.path.basename(content)
            service.create_blob_from_path(container_name,blob_name,content)
            return __make_storage_url(account_name,container_name,blob_name)
        else:
            blob_name = 'unname_blob_uploaded_at' + datetime.now().isoformat().replace('-','_').replace(':','_')[:19]

    if isinstance(content,io.BytesIO):
        service.create_blob_from_stream(container_name,blob_name,content)   
    elif isinstance(content,bytes):
        service.create_blob_from_bytes(container_name,blob_name,content)
    else:
        service.create_blob_from_text(container_name,blob_name,content)
    return __make_storage_url(account_name, container_name, blob_name)
    
def azure_sql_con(**credentials):
    """creates connection to azure sql. use the connection with pandas.read_sql(sql_query,connection)

    Keyword Arguments:
        to override defaults set in environment variable 'AZURE_CREDENTIALS' below keyword arg should be provided
        server {str} -- azure sql server url e.g "myazuresql.database.windows.net" (default:)
        username {str} -- user id (default: admin )
        password {str} -- password (default: admin_password)
        database {str} -- database name (default: dev database)

    Returns:
        pyodbc connection to microsoft azure sql
    """
    credentials = get_azure_credentials(**credentials)
    
    SERVER = credentials.get('server')
    database = credentials.get('database')
    username = credentials.get('username')
    password = credentials.get('password')

    try:
        connection = connect('DRIVER={};SERVER=tcp:{};database={};UID={};PWD={}'.format(
            'ODBC Driver 17 for SQL Server', SERVER, database, username, password))
        logging.info('connected to {} as {}'.format(database, username))
        return connection

    except Exception as e:
        logging.error(e)
        sys.exit('connection to azure sql failed')


def azure_to_df(sql_query, connection=None, **kwargs):
    """ Return result of sql query as pandas dataframe

    Arguments:
        sql_query {str} -- valid SELECT sql statement
        connection {pyodbc connection} -- uses azure_sql_con() function to create connection object
        kwargs {dict} -- key words arguments for pandas.read_sql function

    Returns:
        pandas.DataFrame
    """
    if not connection:
        try:
            connection = azure_sql_con()
        except Exception as e:
            logging.error(
                e)
            logging.error(
                "not connected to azure, please use azure_sql_con() function to create connection object and pass it as a parameter to the function")
    return pd.read_sql(sql_query, connection, **kwargs)

def azure_sql_engine(**credentials):
    """creates sqlalchemy engine to write to azure sql. use the engine with df_to_azure_sql(df,table_name,engine)

    Keyword Arguments:
        to override defaults set in environment variable 'AZURE_CREDENTIALS' below keyword arg should be provided
        server {str} -- azure sql server url e.g "myazuresql.database.windows.net" (default: azure_sql_server)
        username {str} -- user id (default: admin )
        password {str} -- password (default: admin_password)
        database {str} -- database name (default: dev database)

    Returns:
        sqlalchemy engine to microsoft azure sql
    """
    import sqlalchemy
    from urllib.parse import quote_plus
    credentials = get_azure_credentials(**credentials)
    
    server = credentials.get('server')
    database = credentials.get('database')
    username = credentials.get('username')
    password = credentials.get('password')

    connection_string = quote_plus('DRIVER={ODBC Driver 17 for SQL Server}'+f';SERVER=tcp:{server};DATABASE={database};UID={username};PWD={password}')
    engine = sqlalchemy.create_engine(f"mssql+pyodbc:///?odbc_connect={connection_string}")
    logging.info('connected to {} as {}'.format(database, username))
    return engine

def df_to_azure_sql(df, table_name, engine=None, if_exists='fail', index=False, **kwargs):
    """ writes dataframe to Azure sql table
    table_name = 
    if_exists=
    

    Arguments:
        df {pandas.DataFrame} -- data to be written to table
        table_name {str} -- table name e.g 'dbo.fact_sample_superstore'

    Keyword Arguments:
        engine {sqlalchemy.engine} --  (default: {None})
        if_exists {str} -- write mode {'fail', 'replace', 'append'} (default: {'fail'})
        index {bool} -- if true, writes dataframe index as well (default: {False})
        keyword arguments for df.to_sql()

    Returns:
        str -- destination table_name
    """
    
    if not engine:
        logging.debug('instantiating sql achemy engine with default credentials')
        engine = azure_sql_engine()
        
    df.to_sql(table_name,engine,index=index,if_exists=if_exists, **kwargs)
    return table_name

def __parse_storage_url(blob_url):
    account_name, container_name, blob_name = blob_url[8:].split('/')
    account_name = account_name.split('.')[0]
    return account_name, container_name, blob_name

def __make_storage_url(account_name,container_name,blob_name):
    return f'https://{account_name}.blob.core.windows.net/{container_name}/{blob_name}'
