from time import sleep
from pywinauto import mouse
from pywinauto.keyboard import send_keys
from pyautogui import position
from pywinauto.application import Application
import logging
from pywinauto.timings import Timings

Timings.window_find_timeout = 60
logging.disable(logging.CRITICAL) if False else logging.disable(logging.NOTSET)


class QGISCore:
    @staticmethod
    def start_qgis(qgis_filepath, qgis_profile):
        global app
        app = Application(backend='uia').start(cmd_line=f'{qgis_filepath} --profile {qgis_profile}',
                                               timeout=60)
        logging.info(f'[SETUP] QGIS started ({qgis_filepath} --profile {qgis_profile})')

    @staticmethod
    def connect_qgis(qgis_filepath):
        global app
        app = Application(backend='uia').connect(path=qgis_filepath, timeout=60)
        logging.info('[SETUP] QGIS connected')

    @staticmethod
    def kill_qgis():
        app.kill()
        logging.info('[TEARDOWN] QGIS killed')

    @staticmethod
    def main_window():
        global main_window
        main_window = QGISLocator.get_window(app, title_re='.*QGIS.*', found_index=0)
        QGISAction.set_focus_locator(main_window)
        QGISAction.maximize_window(main_window)
        return main_window


class QGISDrawBox:
    @staticmethod
    def draw_box_green(locator, colour='green', thickness=4):
        locator.draw_outline(colour, thickness)

    @staticmethod
    def draw_box_blue(locator, colour='blue', thickness=4):
        locator.draw_outline(colour, thickness)

    @staticmethod
    def draw_box_orange(locator, colour=42215, thickness=4):
        locator.draw_outline(colour, thickness)


class QGISAction(QGISDrawBox):
    @staticmethod
    def set_focus_locator(locator, sleep_time=0):
        QGISDrawBox.draw_box_blue(locator)
        locator.set_focus()
        sleep(sleep_time)
        logging.info(f'[ACTION] {locator.criteria} set focus')

    @staticmethod
    def maximize_window(locator):
        if not locator.is_maximized():
            maximize_button = QGISLocator.get_locator_by_parent_child(locator, title='Maksymalizuj',
                                                                      control_type='Button')
            QGISAction.mouse_click_locator(maximize_button)
            logging.info(f'[ACTION] {locator.criteria} maximized')

    @staticmethod
    def mouse_get_position():
        x, y = position()
        logging.info(f'[ACTION] Mouse position X: {x}, Y: {y} get')
        return x, y

    @staticmethod
    def mouse_click_locator(locator, sleep_time=0):
        QGISDrawBox.draw_box_blue(locator)
        locator.click_input()
        sleep(sleep_time)
        try:
            logging.info(f'[ACTION] {locator.criteria} left clicked, slept {sleep_time} second(s)')
        except AttributeError:
            logging.info(f'[ACTION] {locator.element_info} left clicked, slept {sleep_time} second(s)')

    @staticmethod
    def mouse_right_click_locator(locator, sleep_time=0):
        QGISDrawBox.draw_box_blue(locator)
        locator.right_click_input()
        sleep(sleep_time)
        try:
            logging.info(f'[ACTION] {locator.criteria} right clicked, slept {sleep_time} second(s)')
        except AttributeError:
            logging.info(f'[ACTION] {locator.element_info} right clicked, slept {sleep_time} second(s)')

    @staticmethod
    def mouse_click_expand_locator(locator, sleep_time=0):
        QGISDrawBox.draw_box_blue(locator)
        rectangle = locator.rectangle()
        x = rectangle.left + (rectangle.width() // 2) + 15
        y = rectangle.top + (rectangle.height() // 2)
        mouse.click(coords=(x, y))
        sleep(sleep_time)
        logging.info(f'[ACTION] {locator.criteria} expand clicked, slept {sleep_time} second(s)')

    @staticmethod
    def mouse_click_coords(X, Y, button='left', sleep_time=0):
        mouse.click(coords=(X, Y), button=button)
        sleep(sleep_time)
        logging.info(f'[ACTION] Screen coords X: {X}, Y: {Y} {button} clicked, slept {sleep_time} second(s)')

    @staticmethod
    def mouse_press_coords(X, Y, button='left', sleep_time=0):
        mouse.press(coords=(X, Y), button=button)
        sleep(sleep_time)
        logging.info(f'[ACTION] Screen coords X: {X}, Y: {Y} {button} pressed, slept {sleep_time} second(s)')

    @staticmethod
    def mouse_release_coords(X, Y, button='left', sleep_time=0):
        mouse.release(coords=(X, Y), button=button)
        sleep(sleep_time)
        logging.info(f'[ACTION] Screen coords X: {X}, Y: {Y} {button} released, slept {sleep_time} second(s)')

    @staticmethod
    def mouse_scroll_coords(X, Y, wheel_distance=1, sleep_time=0):
        mouse.scroll(coords=(X, Y), wheel_dist=wheel_distance)
        sleep(sleep_time)
        logging.info(f'[ACTION] Screen coords X: {X}, Y: {Y}, {wheel_distance} scrolled, slept {sleep_time} second(s)')

    @staticmethod
    def select_item_locator(locator, item, sleep_time=0):
        QGISDrawBox.draw_box_blue(locator)
        locator.select(item)
        sleep(sleep_time)
        logging.info(f'[ACTION] {locator.criteria} selected with {item}, slept {sleep_time} second(s)')

    @staticmethod
    def type_keys_locator(locator, keys, sleep_time=0):
        QGISDrawBox.draw_box_blue(locator)
        locator.type_keys(keys)
        sleep(sleep_time)
        logging.info(f'[ACTION] {locator.criteria} typed keys: {keys}, slept {sleep_time} second(s)')

    @staticmethod
    def send_keys(keys, sleep_time=0):
        send_keys(keys)
        sleep(sleep_time)
        logging.info(f'[ACTION] keyboard sent keys: {keys}, slept {sleep_time} second(s)')

    @staticmethod
    def clear_type_keys_enter_locator(locator, keys, sleep_time=0):
        QGISDrawBox.draw_box_blue(locator)
        locator.type_keys('^a{DEL}')
        locator.type_keys(keys)
        locator.type_keys('{ENTER}')
        sleep(sleep_time)
        logging.info(f'[ACTION] CLEAR + {locator.criteria} typed keys: {keys} + ENTER, slept {sleep_time} second(s)')

    @staticmethod
    def clear_type_keys_locator(locator, keys, sleep_time=0):
        QGISDrawBox.draw_box_blue(locator)
        locator.type_keys('^a{DEL}')
        locator.type_keys(keys)
        sleep(sleep_time)
        logging.info(f'[ACTION] CLEAR + {locator.criteria} typed keys: {keys}, slept {sleep_time} second(s)')

    @staticmethod
    def move_mouse_locator(locator, sleep_time=0.5):
        mouse.move(coords=locator.rectangle().mid_point())
        sleep(sleep_time)
        logging.info(f'[ACTION] {locator.criteria} mouse moved, slept {sleep_time} second(s)')

    @staticmethod
    def move_mouse_coords(X, Y, sleep_time=0):
        mouse.move(coords=(X, Y))
        sleep(sleep_time)
        logging.info(f'[ACTION] Screen coords X: {X}, Y: {Y} mouse moved, slept {sleep_time} second(s)')


class QGISLocator(QGISDrawBox):
    @staticmethod
    def get_window(app, **kwargs):
        window = app.window(**kwargs)
        window.wait('exists visible', timeout=60)
        logging.info(f'[WINDOW] {window.criteria} get')
        QGISDrawBox.draw_box_green(window)
        return window

    @staticmethod
    def get_locator_by_parent(parent, **kwargs):
        """ All possible arguments in **kwargs:
        title	Exact title/name of the control (same as .window(title="..."))
        title_re	Regex pattern for the title
        control_type	UI Automation control type (e.g., 'Edit', 'Button')
        auto_id	Automation ID (from Inspect.exe, only in uia backend)
        class_name	Win32 class name of the control
        class_name_re	Regex for class name
        process	Process ID to narrow down control search
        enabled_only	If True (default), only enabled controls are matched
        visible_only	If True (default), only visible controls are matched
        found_index	Zero-based index in case of multiple matches
        ctrl_index	Index used in win32 backend (alternative to found_index)
        handle	Window handle (HWND, integer). Matches control by handle.
        rich_text	Set True to identify RichEdit controls (mainly win32)
        backend	"uia" or "win32" – normally specified at app level
        depth	Max search depth; defaults vary (esp. in recursive search)
        top_level_only	If True, restricts to top-level windows
        control_id	Win32 control ID (not UIA)
        best_match	Internal use — usually inferred from title
        predicate_func	Custom function to filter controls; receives an ElementInfo object

        Example: title='button_name'
        """
        locator = parent.child_window(**kwargs)
        logging.info(f'[LOCATOR] {locator.criteria} get')
        QGISDrawBox.draw_box_green(locator)
        return locator

    @staticmethod
    def get_locator_by_path(path):
        locator = path
        logging.info(f'[LOCATOR] {locator.criteria} get')
        QGISDrawBox.draw_box_green(locator)
        return locator

    @staticmethod
    def get_item(locator, item, exact=False):
        item = locator.get_item(item, exact=exact)
        try:
            logging.info(f'[LOCATOR] {item.criteria} get')
        except AttributeError:
            logging.info(f'[LOCATOR] {item.element_info} get')
        QGISDrawBox.draw_box_green(item)
        return item

    @staticmethod
    def get_locator_exists(locator):
        logging.info(f'[LOCATOR] {locator.criteria} get locator exists')
        QGISDrawBox.draw_box_orange(locator)
        return locator.exists()

    @staticmethod
    def get_locator_is_enabled(locator):
        logging.info(f'[LOCATOR] {locator.criteria} get locator is enabled')
        QGISDrawBox.draw_box_orange(locator)
        return locator.is_enabled()

    @staticmethod
    def get_locator_is_active(locator):
        logging.info(f'[LOCATOR] {locator.criteria} get locator is active')
        QGISDrawBox.draw_box_orange(locator)
        return locator.is_active()

    @staticmethod
    def get_locator_is_readonly(locator):
        logging.info(f'[LOCATOR] {locator.criteria} get locator is readonly')
        QGISDrawBox.draw_box_orange(locator)
        return locator.legacy_properties().get('IsReadOnly', False)

    @staticmethod
    def get_locator_is_visible(locator):
        logging.info(f'[LOCATOR] {locator.criteria} get locator is visible')
        QGISDrawBox.draw_box_orange(locator)
        return locator.is_visible()

    @staticmethod
    def get_locator_texts(locator):
        logging.info(f'[LOCATOR] {locator.criteria} get locator texts')
        QGISDrawBox.draw_box_orange(locator)
        return locator.texts()

    @staticmethod
    def get_selected_text(locator):
        logging.info(f'[LOCATOR] {locator.criteria} get locator selected text')
        QGISDrawBox.draw_box_orange(locator)
        return locator.selected_text()
