from __future__ import print_function

import io
import sqlparse

from collections import deque
from datetime import datetime
from functools import reduce

from snowfinch.connections import *
from snowfinch.parsers.ddl_generators import *
from snowfinch.utils import *

from snowfinch.parsers.commons import *
logger = log.get_logger()


def get_sproc_name(in_file, schma=None):
    sproc_name = None
    full_sproc_name_name = None
    inf = open(in_file, 'r', encoding='utf-8', errors='ignore')
    with inf:
        for line in inf.read().split("\n"):
            if line.lower().lstrip().startswith(tuple(['create', 'alter'])):
                if any(line.strip for _ in ['[', ']']):
                    line = line \
                        .replace('[', '') \
                        .replace(']', '')

                split_words = line.strip().upper().split()
                print(split_words)
                sproc_type = split_words[0]
                sproc_name = split_words[split_words.index('PROCEDURE') + 1] \
                    .replace('DBO.', '')

                # get the full table b=name
                full_sproc_name_name = f"{schma}.{sproc_name}"
    return [sproc_type, sproc_name, full_sproc_name_name]


def get_sproc_header(spname):
    #  inpt_parms = ["--" + param for param in input_parms]
    # inpt_parms = '\n'.join(inpt_parms)

    # datetime object containing current date and time
    now = datetime.now()

    # dd/mm/YY H:M:S
    dtm_string = now.strftime("%Y%m%d%H%M%S")

    sp_hdr = "-- Stored Procedure auto-generated by SnowFinch Framework\n" \
             f"-- Date Created: {dtm_string}\n" \
             f"-- Stored Proc Name : {spname}()\n" \
             "-- email snowfinch-dev@anthem.com for any issues and enhancements\n" \
             f"{'-' * 55}"
    return sp_hdr


def get_sproc_signature(spname, sp_typ, args, ret='VARCHAR NOT NULL'):
    print(f"procedure type is {sp_typ} procedure")
    signature = ''
    if sp_typ.lower() == 'create':

        if args:
            signature = f"CREATE OR REPLACE PROCEDURE {spname}\n" \
                        "(\n\t" \
                        f"{args}\n" \
                        ")\n" \
                        f"RETURNS {ret}\n" \
                        "LANGUAGE JAVASCRIPT\n" \
                        "EXECUTE AS CALLER\n" \
                        "AS\n"
        else:
            signature = f"CREATE OR REPLACE PROCEDURE {spname} ()\n" \
                        f"RETURNS {ret}\n" \
                        "LANGUAGE JAVASCRIPT\n" \
                        "EXECUTE AS CALLER\n" \
                        "AS\n"

    elif sp_typ.lower() == 'alter':
        if args:
            signature = f"ALTER PROCEDURE IF EXISTS {spname}\n" \
                        "(\n\t" \
                        f"{args}\n" \
                        ")\n" \
                        "EXECUTE AS CALLER\n" \
                        "AS\n"
        else:
            signature = f"ALTER PROCEDURE IF EXISTS {spname} ()\n" \
                        "EXECUTE AS CALLER\n" \
                        "AS\n"

    else:
        print("Invalid Stored Procedure Type")
    return "\n" + signature.lstrip()


def get_sproc_args(_inp_):
    inpt = open(_inp_, 'r', encoding='utf-8', errors='ignore')
    spargs = []
    with inpt:
        for arg in inpt:
            if arg.lstrip().startswith('@'):
                arg = arg.replace('@', '').strip()
                if '--' in arg:
                    arg = arg.replace('--', '//')
                spargs.append(arg)
                print(spargs)
            if arg.lstrip().upper().startswith('AS'):
                print(arg)
                break
    return spargs


def get_sproc_begin_end_block(block):
    if block == 'begin':
        block_dlm = f"\n-- BEGIN Block\n" \
                    "$$\n"\
                    "\n// initialize variables\n"\
                    "var js_error_msg: RETURN_MSG: FAILURE: \n" \
                    "var js_success_msg: RETURN_MSG: SUCCESS: \n"\
                    "\n"
    else:
        block_dlm = f"\n-- END Block\n" \
                    "$$\n"
    return block_dlm


def get_sproc_body_begin_kw(_string):
    if is_word_in_string('BEGIN', _string):
        begin_kw = 'BEGIN'
        end_kw = 'END'
    elif is_word_in_string('AS', _string):
        begin_kw = 'AS'
        end_kw = 'END'
    else:
        begin_kw = ''
        end_kw = ''
    return begin_kw, end_kw


def exec_sproc_api(sql_stmt):
    exec_stmt = f"\n\tvar sql_cmd = '{sql_stmt.strip()}';\n\t" \
                "var statement = snowflake.createStatement(sql_cmd);\n" \
                "\tstatement.execute();\n"
    return exec_stmt


def tail(filename, n=10):
    """Return the last n lines of a file"""
    with open(filename) as f:
        last_line = deque(f, n)
        print(last_line[0])
        return last_line


def parse_sproc_begin_blocks(_input, _output, start_kw, end_kw):

    try:
        inp = open(_input, 'r', encoding='utf-8', errors='ignore')
        out = open(_output, 'w+', encoding='utf-8', errors='ignore')
    except FileNotFoundError as err:
        print(f"File {_input} not found", err)
        raise
    except OSError as err:
        print(f"OS error occurred trying to open {_input}", err)
        raise
    except Exception as err:
        print(f"Unexpected error opening {_input} is", repr(err))
        raise
    else:
        with inp, out:
            copy = False
            for _line in inp:
                if _line.upper().startswith(start_kw):
                    copy = True
                elif _line.upper().startswith(end_kw):
                    copy = False
                elif copy:
                    if start_kw in _line.upper():
                        _line = _line.replace(start_kw, 'try {')
                    if _line.upper() == end_kw:
                        msg = '} \n catch (err) {\n\t return js_error_msg + err;\n\t}'
                        print(msg)
                        _line = _line.replace(end_kw, msg)
                    out.write(_line)


def parse_sproc_begin_blocks1(_input, _output, start_kw, end_kw):
    # inp = open(_input, 'r', encoding='utf-8', errors='ignore')
    out = open(_output, 'w+', encoding='utf-8', errors='ignore')

    with out:
        copy = False
        for _line in io.StringIO(_input):
            if _line.upper().startswith(start_kw):
                copy = True
            elif _line.upper().startswith(end_kw):
                copy = False
            elif copy:
                if 'BEGIN' in _line.upper():
                    _line = _line.replace(start_kw, 'try {')
                if _line.upper() == 'END':
                    msg = '} \n catch (err) {\n\t return js_error_msg + err;\n\t}'
                    print(msg)
                    _line = _line.replace(end_kw, msg)
                print(_line)
                out.write(_line)


def get_begin_try_catch_block(_input, _output):
    begintry = parse_sproc_begin_blocks(_input, _output, 'begin try', 'end try')
    print()
    begin_try = "// begin try block\n" \
                "try {\n" \
                f"{begintry}\n" \
                "// end  of try block\n" \
                "}\n"
    print(begin_try)

    # for line in inp:
    #     if line.strip().lower() == 'begin  catch':
    #         begincatch = parse_sproc_begin_blocks(line, 'begin  catch', 'end  catch')
    #         begin_catch = "// begin catch block\n" \
    #                       "catch(err){\n" \
    #                       f"{begincatch}\n" \
    #                       "// end  catch block\n" \
    #                       "}\n"
    #         return begin_catch


def parse_sproc_body(_input, _output, header_block, end_block):
    inp = open(_input, 'r', encoding='utf-8', errors='ignore')
    out = open(_output, 'w+', encoding='utf-8', errors='ignore')

    with inp, out:
        # write the procedure signature here
        out.write(header_block)

        copy = False
        for line in inp:
            if line.strip().upper().startswith("BEGIN"):
                copy = True
            elif line.strip().upper().startswith("END"):
                copy = False
            elif copy:
                if line.strip().upper().startswith('DECLARE'):
                    line = line.replace('DECLARE', '').replace('@', 'var ').replace(',', ';')
                if line.strip().startswith('@'):
                    line = line.replace('@', 'var').replace(',', ';')
                if line.strip().upper().startswith('SELECT'):
                    stmt = line.replace('@', '')
                    line = exec_sproc_api(stmt)
                if line.strip().upper().startswith('SET'):
                    if '@' in line:
                        line = line.replace('@', '')
                if line.strip().upper().startswith('IF'):
                    if '@' in line:
                        line = line.strip().replace('@', '')
                    if 'SET' in line.upper():
                        line = line.strip().upper()\
                            .replace('IF', 'IF (')\
                            .replace('SET', ') { SET') + '};\n'

                # write the process body block here
                out.write(line)
        # write the end block of the process
        out.write(end_block)


def split_keep_sep(s, sep):
    return reduce(
        lambda acc, elem: acc[:-1] + [acc[-1] + elem]
        if elem == sep else acc + [elem],
        re.split("(%s)" % re.escape(sep), s), [])


def parse_sproc_body1(_input, _output, header_block, end_block):
    inp = open(_input, 'r', encoding='utf-8', errors='ignore')
    out = open(_output, 'w+', encoding='utf-8', errors='ignore')

    with inp, out:
        # write the procedure signature here
        out.write(header_block)

        # for line in inp.read().split(';'):
        for line in split_keep_sep(inp.read(), ';'):
            if 'DECLARE' in line.upper():

                if '= (SELECT' in line.upper():
                    print(line)
                    line = line\
                        .replace('DECLARE', '')\
                        .replace('@', 'var ')\
                        .replace('(SELECT', 'snowflake.execute({\n\tsqlText: "')\
                        .replace(';', ' });\n')

                else:
                    line = line \
                        .replace('DECLARE', '') \
                        .replace('@', 'var ')\
                        .replace(',', ';\n')

            if line.strip().startswith('@'):
                line = line.replace('@', 'var').replace(',', ';')

            if line.strip().upper().startswith(tuple(['SELECT', 'DROP', 'CREATE', 'INSERT', 'UPDATE', 'MERGE'])):
                stmt = line.replace('@', '')
                line = exec_sproc_api(stmt)

            if line.strip().upper().startswith('SET'):
                if '@' in line:
                    line = line.replace('@', '')

            if line.strip().upper().startswith('IF'):
                if '@' in line:
                    line = line.replace('@', '')
                if 'SET' in line.upper():
                    line = line.strip().upper()\
                        .replace('IF', '\n\tIF (')\
                        .replace('SET', ') { SET').replace(';', '') + '};\n'

            # write the process body block here
            out.write(line)
        # write the end block of the process
        out.write(end_block)


def parse_sproc_body2(_input, _output, header_block, end_block):
    # inp = open(_input, 'r', encoding='utf-8', errors='ignore')
    out = open(_output, 'w+', encoding='utf-8', errors='ignore')

    with out:
        # write the procedure signature here
        out.write(header_block)

        # for line in inp.read().split(';'):
        # for _line in io.StringIO(_input):
        for line in split_keep_sep(io.StringIO(_input), ';'):
            if 'DECLARE' in line.upper():

                if '= (SELECT' in line.upper():
                    print(line)
                    line = line\
                        .replace('DECLARE', '')\
                        .replace('@', 'var ')\
                        .replace('(SELECT', 'snowflake.execute({\n\tsqlText: "')\
                        .replace(';', ' });\n')

                else:
                    line = line \
                        .replace('DECLARE', '') \
                        .replace('@', 'var ')\
                        .replace(',', ';\n')

            if line.strip().startswith('@'):
                line = line.replace('@', 'var').replace(',', ';')

            if line.strip().upper().startswith(tuple(['SELECT', 'DROP', 'CREATE', 'INSERT', 'UPDATE', 'MERGE'])):
                stmt = line.replace('@', '')
                line = exec_sproc_api(stmt)

            if line.strip().upper().startswith('SET'):
                if '@' in line:
                    line = line.replace('@', '')

            if line.strip().upper().startswith('IF'):
                if '@' in line:
                    line = line.replace('@', '')
                if 'SET' in line.upper():
                    line = line.strip().upper()\
                        .replace('IF', '\n\tIF (')\
                        .replace('SET', ') { SET').replace(';', '') + '};\n'

            # write the process body block here
            out.write(line)
        # write the end block of the process
        out.write(end_block)


def get_sproc_body(_input, _output):
    try:
        inp = open(_input, 'r', encoding='utf-8', errors='ignore')
        out = open(_output, 'w+', encoding='utf-8', errors='ignore')
    except FileNotFoundError as err:
        print(f"File {_input} not found", err)
        raise
    except OSError as err:
        print(f"OS error occurred trying to open {input}", err)
        raise
    except Exception as err:
        print(f"Unexpected error opening {input} is", repr(err))
        raise
    else:
        with inp, out:
            count = 0
            for statement in inp:

                if any(statement.strip for _ in ['[', ']']):
                    statement = statement \
                        .replace('[', '') \
                        .replace(']', '')

                if '@' in statement.strip():
                    statement = statement.replace('@', '')

                if statement.strip().upper().startswith('IF OBJECT_ID(\'TEMPDB..#'):
                    statement = ''.strip()

                if statement.strip().upper().startswith('IF'):
                    count += 1
                    print(count)
                    statement = statement + "\n\t{ // if block start\n"

                if statement.strip().upper().startswith('IF') and count > 1:
                    statement = '\n\t} // if block end\n' + statement

                if statement.lstrip().upper().startswith('DROP TABLE #'):
                    drop_sql = statement.replace('#', 'IF  EXISTS ')
                    statement = exec_sproc_api(drop_sql)

                if statement.lstrip().upper().startswith('BEGIN TRANSACTION'):
                    statement = statement.replace('TRANSACTION', 'TRANSACTION;')

                if statement.strip().upper() == 'COMMIT TRANSACTION':
                    statement = statement.replace('TRANSACTION', ';')

                if statement.strip().upper() == 'ROLLBACK TRANSACTION':
                    statement = statement.replace('TRANSACTION', ';')

                if statement.strip().upper() == 'BEGIN TRY':
                    statement = statement.upper().replace('BEGIN TRY', '// begin try block\ntry {')
                if statement.strip() == 'END TRY':
                    statement = statement.upper().replace('END TRY', '} // end try block')

                if statement.strip().upper() == 'BEGIN CATCH':
                    statement = statement.upper().replace('BEGIN CATCH', '// begin catch block\ncatch (err) {')

                if statement.strip() == 'END CATCH':
                    statement = statement.upper().replace('END CATCH', '} // end catch block')
                print(statement)

                # finally write it to output file
                out.write(statement)


def build_sproc_body(_infile):
    # get SP name, type
    sp_name = get_sproc_name(_infile, 'VBC')[2]
    # print(sp_name)
    sp_type = get_sproc_name(_infile, 'VBC')[0].lower()
    # print(sp_type)

    # get sp arguments
    sp_args = get_sproc_args(_infile)
    params = '\n\t'.join(sp_args)
    # print(params)

    # build sp header
    sp_header = get_sproc_header(sp_name)

    # build sp signature
    sp_sig = get_sproc_signature(sp_name, sp_type, params)
    # print(sig)

    sp_begin = get_sproc_begin_end_block('begin')
    sp_end = get_sproc_begin_end_block('end')

    sp_header_block = sp_header + sp_sig + sp_begin
    print(sp_header_block)

    out_body_raw = get_outfile_full_name(_infile, 'raw')
    # get_sproc_body(infile, out_raw2, 'D01_VBC', 'VBC')

    parse_sproc_begin_blocks(_infile, out_body_raw, "BEGIN", "END")

    out_body_tmp = get_outfile_full_name(_infile, 'tmp')
    parse_sproc_body1(out_body_raw, out_body_tmp, sp_header_block, sp_end)

    # parse_sproc_body(infile, out_body, sp_header_block, sp_end)


def build_sproc_mssql(config):

    # get SnowFlake configs
    # sf_user = config['snowflake']['user'].get()
    sf_wh = config['snowflake']['warehouse'].get()
    sf_db = config['snowflake']['database'].get()
    sf_table_owner_role = config['snowflake']['owner_role'].get()
    sf_schema = config['snowflake']['schema'].get()

    # get mssql configs
    sproc_db = config['mssql']['database'].get()
    sproc_schema = config['mssql']['schema'].get()
    sproc_names = config['mssql']['sproc_names'].get()
    sproc_temp_dir = config['mssql']['sproc_temp_dir'].get()
    sproc_out_dir = config['mssql']['sproc_out_dir'].get()
    object_type = 'procedure'

    # get the connections
    src_conn = get_source_db_connections('mssql', config)
    tgt_conn = snowfinch_connections("snowflake", config)

    for proc in sproc_names:
        logger.info("\n\n")
        logger.info(f"{'*' * 55}")
        logger.info(f"Processing stored procedure {proc}")
        logger.info(f"{'*'*55}")

        tmp_output_file = f"{sproc_temp_dir}/{proc}.tmp"
        final_output_file = f"{sproc_out_dir}/{proc}.sql"
        logger.info("\noutput files...")
        logger.info(f"temp output file: {tmp_output_file}")
        logger.info(f"final output file: {final_output_file}")

        # read stored proc source code
        logger.info("\nFetching stored proc definition...")
        sp_sql = get_sproc_def('mssql', sproc_db, sproc_schema, proc)

        rawdf = read_sql(sp_sql, src_conn)
        lst = rawdf['ROUTINE_DEFINITION'].astype(str).tolist()
        sp_ddl = '\n'.join(map(str, lst))

        sp_ddl_clean = sqlparse.format(sp_ddl, strip_comments=True).strip()

        # get sp type
        sp_type = sp_ddl_clean.split(' ', 1)[0]

        # identify the begin block
        logger.info("\nIdentifying the begin blocks...")
        begin_key, end_key = get_sproc_body_begin_kw(sp_ddl_clean)
        logger.info(begin_key)
        logger.info(end_key)

        logger.info("\nFetching the stored procedure args...")
        args_sql = get_sproc_args_mssql(sproc_db, sproc_schema, proc)

        args_df = read_sql(args_sql, src_conn)
        logger.info("\nsp arguments")

        if len(args_df.index) == 0:
            args_list_str = ''
            args_list = ''
        else:
            args_lst = args_df['PARAM'].astype(str).tolist()
            print(args_lst)
            args_list_str = ',\n'.join(map(str, args_lst))
            args_list = ','. join(map(str, [i.split()[1] for i in args_lst]))

        logger.info(args_list_str)
        logger.info(args_list)

        # build sp header
        sp_header = get_sproc_header(proc)

        # build sp signature
        sp_full_name = f"{sf_schema}.{proc}"
        sp_sig = get_sproc_signature(sp_full_name, sp_type, args_list_str)

        sp_begin = get_sproc_begin_end_block('begin')
        sp_end = get_sproc_begin_end_block('end')

        sp_header_block = sp_header + sp_sig + sp_begin

        parse_sproc_begin_blocks1(sp_ddl_clean, tmp_output_file, begin_key, end_key)
        parse_sproc_body1(tmp_output_file, final_output_file, sp_header_block, sp_end)

        statements = get_sql_stmt_list(final_output_file)
        print(statements)

        # with open(final_output_file, 'r', encoding='utf-8') as file:
        with tgt_conn.connect() as tconn:

            #  set the warehouse and db
            tconn.execute(f'use warehouse {sf_wh}')
            tconn.execute(f'use database {sf_db}')

            #  execute the statement
            tconn.execute(statements)

            # Change the ownership of the table
            sp_name = f"{proc}({args_list})"
            grant_ownership(tconn, sp_name, object_type, sf_schema, sf_table_owner_role)

            # Check the table metadata to confirm  table creation and ownership
            get_object_metadata(tconn, proc, object_type, sf_schema)

            logger.info(f"Successfully created {sp_full_name} on SnowFlake\n")


# if __name__ == "__main__":
#     # initiate the configuration
#     config = confuse.Configuration('SnowFinch', __name__)
#
#     # Add conf items from specified file
#     config.set_file('/Users/ag29266/Downloads/pims/snowfinch-vbc-dev.yaml')
#
#     build_sproc_mssql(config)
