import asyncio
import inspect
import warnings
from dataclasses import dataclass, field
from functools import partial
from multiprocessing import Queue
from typing import Any, Callable, Dict, Optional, Tuple

from .dataclasses import KWONLY_SLOTS
from .globals import log

method_queue: Queue = Queue()
response_queue: Queue = Queue()

try:
    with warnings.catch_warnings():
        # webview depends on bottle which uses the deprecated CGI function (https://github.com/bottlepy/bottle/issues/1403)
        warnings.filterwarnings('ignore', category=DeprecationWarning)
        import webview
        from webview.window import FixPoint

    class WindowProxy(webview.Window):

        def __init__(self) -> None:  # pylint: disable=super-init-not-called
            pass  # NOTE we don't call super().__init__ here because this is just a proxy to the actual window

        async def get_always_on_top(self) -> bool:
            """Get whether the window is always on top."""
            return await self._request()

        def set_always_on_top(self, on_top: bool) -> None:
            """Set whether the window is always on top."""
            self._send(on_top)

        async def get_size(self) -> Tuple[int, int]:
            """Get the window size as tuple (width, height)."""
            return await self._request()

        async def get_position(self) -> Tuple[int, int]:
            """Get the window position as tuple (x, y)."""
            return await self._request()

        def load_url(self, url: str) -> None:
            self._send(url)

        def load_html(self, content: str, base_uri: str = ...) -> None:  # type: ignore
            self._send(content, base_uri)

        def load_css(self, stylesheet: str) -> None:
            self._send(stylesheet)

        def set_title(self, title: str) -> None:
            self._send(title)

        async def get_cookies(self) -> Any:  # pylint: disable=invalid-overridden-method
            return await self._request()

        async def get_current_url(self) -> str:  # pylint: disable=invalid-overridden-method
            return await self._request()

        def destroy(self) -> None:
            self._send()

        def show(self) -> None:
            self._send()

        def hide(self) -> None:
            self._send()

        def set_window_size(self, width: int, height: int) -> None:
            self._send(width, height)

        def resize(self, width: int, height: int, fix_point: FixPoint = FixPoint.NORTH | FixPoint.WEST) -> None:
            self._send(width, height, fix_point)

        def minimize(self) -> None:
            self._send()

        def restore(self) -> None:
            self._send()

        def toggle_fullscreen(self) -> None:
            self._send()

        def move(self, x: int, y: int) -> None:
            self._send(x, y)

        async def evaluate_js(self, script: str) -> str:  # pylint: disable=arguments-differ,invalid-overridden-method
            return await self._request(script)

        async def create_confirmation_dialog(self, title: str, message: str) -> bool:  # pylint: disable=invalid-overridden-method
            return await self._request(title, message)

        async def create_file_dialog(  # pylint: disable=invalid-overridden-method
            self,
            dialog_type: int = webview.OPEN_DIALOG,
            directory: str = '',
            allow_multiple: bool = False,
            save_filename: str = '',
            file_types: Tuple[str, ...] = (),
        ) -> Tuple[str, ...]:
            return await self._request(
                dialog_type=dialog_type,
                directory=directory,
                allow_multiple=allow_multiple,
                save_filename=save_filename,
                file_types=file_types,
            )

        def expose(self, function: Callable) -> None:  # pylint: disable=arguments-differ
            raise NotImplementedError(f'exposing "{function}" is not supported')

        def _send(self, *args: Any, **kwargs: Any) -> None:
            name = inspect.currentframe().f_back.f_code.co_name  # type: ignore
            method_queue.put((name, args, kwargs))

        async def _request(self, *args: Any, **kwargs: Any) -> Any:
            def wrapper(*args: Any, **kwargs: Any) -> Any:
                try:
                    method_queue.put((name, args, kwargs))
                    return response_queue.get()  # wait for the method to be called and writing its result to the queue
                except Exception:
                    log.exception(f'error in {name}')
                    return None
            name = inspect.currentframe().f_back.f_code.co_name  # type: ignore
            return await asyncio.get_event_loop().run_in_executor(None, partial(wrapper, *args, **kwargs))

        def signal_server_shutdown(self) -> None:
            self._send()

except ModuleNotFoundError:
    class WindowProxy:  # type: ignore
        pass  # just a dummy if webview is not installed


@dataclass(**KWONLY_SLOTS)
class Native:
    start_args: Dict[str, Any] = field(default_factory=dict)
    window_args: Dict[str, Any] = field(default_factory=dict)
    main_window: Optional[WindowProxy] = None
