########################################################################################################################
# IMPORTS

import asyncio
import logging
import threading
from datetime import timedelta
from typing import Optional, Self, Sequence

from datamarket.interfaces.proxy import ProxyInterface

from .async_api import NodriverResponse
from .async_api import SeleniumCrawler as _AsyncSeleniumCrawler
from .async_api import human_press_key as _async_human_press_key
from .async_api import human_type as _async_human_type

########################################################################################################################
# SETUP LOGGER

logger = logging.getLogger(__name__)


########################################################################################################################
# SYNC HELPER FUNCTIONS


def human_type(crawler: "SeleniumCrawler", text: str, delay: int = 100):
    """Types text character-by-character with randomized delays. Requires the sync SeleniumCrawler instance."""
    crawler._run(_async_human_type(crawler.tab, text, delay))


def human_press_key(
    crawler: "SeleniumCrawler", key: str, count: int = 1, delay: int = 100, add_sleep: bool = True
) -> None:
    """Presses a key with a random delay. Requires the sync SeleniumCrawler instance."""
    crawler._run(_async_human_press_key(crawler.tab, key, count, delay, add_sleep))


########################################################################################################################
# PUBLIC CLASS


class SeleniumCrawler:
    """Sync nodriver-based crawler. Drop-in replacement for PlaywrightCrawler using Chrome via nodriver.

    Wraps the async SeleniumCrawler with a dedicated background event loop thread,
    since nodriver is an async-only library.
    """

    def __init__(self, proxy_interface: Optional[ProxyInterface] = None, headless: bool = True):
        self._loop = asyncio.new_event_loop()
        self._thread = threading.Thread(target=self._loop.run_forever, daemon=True)
        self._thread.start()
        self._crawler = _AsyncSeleniumCrawler(proxy_interface, headless)

    @property
    def tab(self):
        return self._crawler.tab

    @property
    def browser(self):
        return self._crawler.browser

    def _run(self, coro):
        """Submit a coroutine to the background event loop and block until complete."""
        return asyncio.run_coroutine_threadsafe(coro, self._loop).result()

    def __enter__(self) -> Self:
        self._run(self._crawler.__aenter__())
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        try:
            self._run(self._crawler.__aexit__(exc_type, exc_val, exc_tb))
        except Exception as e:
            logger.warning(f"Error during cleanup: {e}")
        finally:
            self._loop.call_soon_threadsafe(self._loop.stop)
            self._thread.join(timeout=10)
            self._loop.close()

    def init_context(self) -> Self:
        """Initializes a new browser instance."""
        self._run(self._crawler.init_context())
        return self

    def restart_context(self) -> None:
        """Closes the current browser and initializes a new one."""
        self._run(self._crawler.restart_context())

    def goto(
        self,
        url: str,
        max_proxy_delay: timedelta = timedelta(minutes=10),
        timeout: int = 30_000,
    ) -> NodriverResponse:
        """Ensures the browser is initialized and navigates to the given URL."""
        return self._run(self._crawler.goto(url, max_proxy_delay, timeout))

    def get_data(
        self,
        url: str,
        output: str = "json",
        sleep: tuple = (6, 3),
        max_proxy_delay: timedelta = timedelta(minutes=10),
        ignored_status_codes: Sequence[int] = (),
        timeout: int = 30_000,
        **kwargs,
    ):
        """Crawls a given URL using nodriver and attempts to parse its body content."""
        return self._run(
            self._crawler.get_data(url, output, sleep, max_proxy_delay, ignored_status_codes, timeout, **kwargs)
        )
