import win32com.client
import pythoncom
import threading
import subprocess
import pandas as pd
import time
import os
from typing import Self

SAP_APP_PATH = r'C:\Program Files (x86)\SAP\FrontEnd\SAPgui\\'
SAP_APP_FILE = 'saplogon.exe'
SAP_TMP_PATH = r'C:\temp\\'
SAP_TMP_FILE = 'tmp.txt'
SAP_ACTIVE_SESSION = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}


class SapGui:
    """
    Class to use python to operate in SAP using SAP GUI Scripting.
    Please provide SAP module name as argument.
    sap_module variable should be in form of dictionary: {Module Name: Module Code}
    """
    def __init__(self, sap_module: dict) -> None:
        self.__sap_module = sap_module
        self.__sap_module_opened = False
        self.__sap_gui_auto = None
        self.__application = None
        self.__connection = None
        self.__session = None
        self.__sap_open()

    def __sap_open(self) -> Self:
        """
        Method to open SAP module to operate on it.
        """
        try:
            if SAP_APP_FILE not in str(subprocess.check_output('tasklist')):
                subprocess.Popen(SAP_APP_PATH+SAP_APP_FILE)
                time.sleep(3)
            self.__sap_gui_auto = win32com.client.GetObject("SAPGUI")
            self.__application = self.__sap_gui_auto.GetScriptingEngine

            if self.__application.Connections.Count > 0:
                for i in range(0, self.__application.Connections.Count):
                    self.__connection = self.__application.Children(i)
                    info = self.__connection.Children(0).info.client
                    if info == list(self.__sap_module.values())[0]:
                        self.__session = self.__connection.Children(0)
                        self.__session.findById("wnd[0]").sendVKey(0)
                        self.__sap_module_opened = True
                        break
            if self.__sap_module_opened is not True:
                self.__connection = self.__application.OpenConnection(
                    list(self.__sap_module.keys())[0])
                self.__session = self.__connection.Children(0)
                self.__session.findById("wnd[0]").sendVKey(0)
                self.__sap_module_opened = True
        except Exception as e:
            print(e)
            self.__sap_module_opened = False
        return self

    def sap_open_check(self) -> bool:
        """
        Method to check if SAP module was opened successfully.
        :return: true if SAP module was opened, false if not.
        """
        return self.__sap_module_opened

    def sap_run(self, func, *args, **kwargs) -> Self:
        """
        Method to run generated SAP GUI script.
        :param func: function generated by SAP GUI Scripting.
        :param args: function parameters.
        :return: none.
        """
        if self.__sap_module_opened:
            func(self.__session, *args, **kwargs)
        return self

    def sap_close_all_session(self) -> None:
        self.__connection.CloseConnection()


def sap_run_new_session(func, *args, **kwargs) -> None:
    def sap_run_tread(*arguments):
        pythoncom.CoInitialize()
        win32com.client.Dispatch(pythoncom.CoGetInterfaceAndReleaseStream(
            arguments[0], pythoncom.IID_IDispatch))
        sap_gui_auto = win32com.client.GetObject("SAPGUI")
        application = sap_gui_auto.GetScriptingEngine
        connection = application.Children(0)
        while 0 not in SAP_ACTIVE_SESSION.values():
            time.sleep(10)
        for session_id, session_active in SAP_ACTIVE_SESSION.items():
            if session_active == 0:
                SAP_ACTIVE_SESSION[session_id] = 1
                session = connection.Children(0)
                session.createsession()
                time.sleep(2)
                session = connection.Children(session_id)
                func(session, *args, **kwargs)
                session.findById("wnd[0]").sendVKey(15)
                SAP_ACTIVE_SESSION[session_id] = 0
                break

    pythoncom.CoInitialize()
    xl = win32com.client.Dispatch('Excel.Application')
    xl_id = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, xl)
    t1 = threading.Thread(target=sap_run_tread, args=[xl_id])
    time.sleep(3)
    t1.start()


def sap_close() -> None:
    """
    Method to close all SAP instances.
    :return: none.
    """
    os.system(f'taskkill /F /IM {SAP_APP_FILE}')


def sap_download_tmp_file_del() -> None:
    """
    Funtion to remove temporary downloaded file according to variable SAP_TMP_FILE.
    :return: None
    """
    if os.path.isfile(SAP_TMP_PATH+SAP_TMP_FILE):
        os.remove(SAP_TMP_PATH+SAP_TMP_FILE)


def sap_download_tmp_files_del(sap_tmp_files: list) -> None:
    """
    Function to remove indicated list of downloaded files.
    :param sap_tmp_files: list of files
    :return: None
    """
    for file in sap_tmp_files:
        if os.path.isfile(SAP_TMP_PATH+file):
            os.remove(SAP_TMP_PATH+file)


def sap_download_tmp_file(header_row: int, tmp_file_del: bool = True,
                          dtypes: dict = None) -> pd.DataFrame:
    """
    Function to extract downloaded SAP file to pandas dataframe.
    :param header_row: number of row where is header fo data
    :param tmp_file_del: fill which need to be extracted, default SAP_TMP_FILE.
    :param dtypes: dictionary to indicate what type proper column should have
    :return: None
    """
    df = pd.read_csv(SAP_TMP_PATH+SAP_TMP_FILE, header=header_row,
                     delimiter="\t", encoding='unicode_escape',
                     on_bad_lines='skip', dtype=dtypes)
    label_drop = [x for x in df if 'Unnamed' in x]
    df.drop(label_drop, axis=1, inplace=True)
    if tmp_file_del:
        sap_download_tmp_file_del()
    return df