import logging
import os
import sys
import threading
import time
import traceback
from multiprocessing import Queue
from typing import Any, Callable, Dict, Iterable, Optional
from uuid import uuid4

import pytest

logger = logging.getLogger('pywebview')


def run_test(
    webview: Any,
    window: Any,
    thread_func: Optional[Callable] = None,
    param: Iterable = (),
    start_args: Dict[str, Any] = {},
    no_destroy: bool = False,
    destroy_delay: float = 0,
    debug: bool = False,
) -> None:
    """ "
    A test running function that creates a window and runs a thread function in it. Test logic is to be placed in the
    thread function. The function will wait for the thread function to finish and then destroy the window. If the thread
    function raises an exception, the test will fail and the exception will be printed.

    :param webview: webview module
    :param window: window instance created with webview.create_window
    :param thread_func: function to run in the window.
    :param param: positional arguments to pass to the thread function
    :param start_args: keyword arguments to pass to webview.start
    :param no_destroy: flag indicating whether to destroy the window after the thread function finishes (default: False).
    If set to True, the window will not be destroyed and the test will not finish until the window is closed manually.
    :param destroy_delay: delay in seconds before destroying the window (default: 0)
    :param debug: flag indicating whether to enable debug mode (default: False)

    """
    __tracebackhide__ = True
    try:
        queue: Queue = Queue()

        if debug:
            start_args = {**start_args, 'debug': True}

        time.sleep(1)
        create_test_window(
            webview, window, thread_func, queue, param, start_args, no_destroy, destroy_delay
        )

        if not queue.empty():
            e = queue.get()
            pytest.fail(e)

    except Exception as e:
        pytest.fail(e)


def assert_js(window, func_name, expected_result, *func_args):
    """
    A helper function to run a Javascript function in the window and assert the result.

    @param window: window instance created with webview.create_window
    @param func_name: the name of the Javascript function to run
    @param expected_result: the expected result of the function
    @param func_args: arguments to pass to the function

    """
    window.events.loaded.wait()
    value_id = 'v' + uuid4().hex[:8]
    func_args = str(func_args).replace(',)', ')')

    execute_func = f"""
    window.pywebview.api.{func_name}{func_args}.then(function(value) {{
        window.{value_id} = value
    }}).catch(function() {{
        window.{value_id} = 'error'
    }})
    """
    check_func = f'window.{value_id}'

    window.evaluate_js(execute_func)

    result = window.evaluate_js(check_func)
    counter, MAX = 0, 50

    while result is None:
        if counter == MAX:
            raise AssertionError('assert_js timeout')
        else:
            counter += 1
            time.sleep(0.1)
            result = window.evaluate_js(check_func)

    assert expected_result == result


def create_test_window(
    webview, window, thread_func, queue, thread_param, start_args, no_destroy, destroy_delay
):
    def thread():
        try:
            window.events.loaded.wait()
            if thread_func:
                thread_func(window, *thread_param)
            destroy_event.set()
        except Exception as e:
            logger.exception(e, exc_info=True)
            queue.put(traceback.format_exc())
            destroy_event.set()

    if not no_destroy:
        destroy_event = _destroy_window(webview, window, destroy_delay)

        t = threading.Thread(target=thread)
        t.start()

    webview.start(**start_args)


def get_test_name():
    return os.environ.get('PYTEST_CURRENT_TEST').split(':')[-1].split(' ')[0]


def wait_release(lock, timeout=3, count=0):
    """
    Release a lock only if after it is locked. If the lock is not released within the timeout, return False.
    """
    if lock.locked():
        lock.release()
        return True
    elif count * 0.1 < timeout:
        time.sleep(0.1)
        wait_release(lock)
    else:
        return False


def take_screenshot():
    if sys.platform == 'darwin':
        from datetime import datetime
        from subprocess import Popen

        os.makedirs('/tmp/screenshots', exist_ok=True)
        Popen(
            ['screencapture', '-x', f'/tmp/screenshots/screenshot-{datetime.now().timestamp()}.png']
        ).wait()


def _destroy_window(_, window, delay):
    def stop():
        try:
            event.wait()
            time.sleep(delay)
            window.destroy()

        except Exception as e:
            logger.exception(e, exc_info=True)

    event = threading.Event()
    event.clear()
    t = threading.Thread(target=stop)
    t.start()

    return event


def take_screenshot2():
    from datetime import datetime

    from PIL import ImageGrab

    # Create the directory if it doesn't exist
    save_dir = '/tmp/screenshots'
    os.makedirs(save_dir, exist_ok=True)

    # Get current timestamp for unique file name
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    file_name = f'screenshot_pil_{timestamp}.png'

    # Full path to save the screenshot
    save_path = os.path.join(save_dir, file_name)

    # Take the screenshot
    screenshot = ImageGrab.grab()

    # Save the screenshot
    screenshot.save(save_path, 'PNG')

    print(f'Screenshot saved at: {save_path}')
    return save_path
