"""Unified WebView controller for cross-protocol automation."""

from __future__ import annotations

import time
from typing import TYPE_CHECKING, Any

from nomo_e2e.adapters.cdp import CdpAdapter
from nomo_e2e.adapters.webdriver import WebDriverAdapter
from nomo_e2e.exceptions import WebViewOperationError

if TYPE_CHECKING:
    from nomo_e2e.adapters.base import BaseAdapter

DRIVER_CDP = "cdp"
DRIVER_WEBDRIVER = "webdriver"
DRIVER_AUTO = "auto"


class WebViewController:
    """Unified controller for WebView operations.

    Supports both CDP and WebDriver protocols. The protocol is automatically
    selected based on the endpoint or can be explicitly specified.

    Example:
        >>> from nomo_e2e import WebViewController
        >>>
        >>> # CDP mode (e.g., Electron with --remote-debugging-port)
        >>> controller = WebViewController.connect(
        ...     "http://localhost:9222", driver="cdp"
        ... )
        >>>
        >>> # WebDriver mode (e.g., Tauri with tauri-plugin-webdriver)
        >>> controller = WebViewController.connect(
        ...     "http://127.0.0.1:4445", driver="webdriver"
        ... )
        >>>
        >>> # Perform actions
        >>> controller.click("#submit-btn")
        >>> controller.type_text("#username", "admin")
        >>> controller.screenshot("/tmp/page.png")
        >>>
        >>> # Cleanup
        >>> controller.disconnect()
    """

    def __init__(
        self,
        adapter: "BaseAdapter",
        driver: str,
    ) -> None:
        """Initialize controller with an adapter.

        Args:
            adapter: Protocol-specific adapter instance.
            driver: Driver type ("cdp" or "webdriver").
        """
        self._adapter = adapter
        self._driver = driver

    @staticmethod
    def connect(endpoint: str, driver: str = DRIVER_AUTO) -> "WebViewController":
        """Connect to endpoint and create controller.

        Args:
            endpoint: WebView debug endpoint URL.
                - CDP: "http://localhost:9222" (Electron/WebView2)
                - WebDriver: "http://127.0.0.1:4445" (Tauri)
            driver: Protocol to use. Options:
                - "auto": Auto-select based on endpoint (default)
                - "cdp": Force CDP protocol
                - "webdriver": Force WebDriver protocol

        Returns:
            WebViewController instance connected to the endpoint.

        Raises:
            WebViewOperationError: If connection fails or driver is unsupported.
            WebViewConnectionError: If unable to establish connection.

        Example:
            >>> controller = WebViewController.connect(
            ...     "http://localhost:9222",
            ...     driver="cdp"
            ... )
        """
        resolved_driver = WebViewController._resolve_driver(driver, endpoint)

        if resolved_driver == DRIVER_CDP:
            adapter: "BaseAdapter" = CdpAdapter(endpoint)
        else:
            adapter = WebDriverAdapter(endpoint)

        adapter.connect()
        return WebViewController(adapter, resolved_driver)

    @staticmethod
    def _resolve_driver(driver: str, endpoint: str = None) -> str:
        """Resolve driver string to canonical form."""
        normalized = driver.strip().lower()
        if normalized == DRIVER_AUTO:
            if endpoint:
                normalized_endpoint = endpoint.lower()
                if any(indicator in normalized_endpoint for indicator in [
                    "localhost:9222", "127.0.0.1:9222",
                    "remote-debugging", "devtools",
                ]):
                    return DRIVER_CDP
            return DRIVER_WEBDRIVER
        if normalized in {DRIVER_CDP, DRIVER_WEBDRIVER}:
            return normalized
        from nomo_e2e.exceptions import WebViewOperationError

        raise WebViewOperationError(f"Unsupported driver type: {driver}")

    @property
    def driver(self) -> str:
        """Get the current driver type.

        Returns:
            Driver type string ("cdp" or "webdriver").
        """
        return self._driver

    def click(self, selector: str, timeout_ms: int = 30000) -> None:
        """Click an element by CSS selector.

        Args:
            selector: CSS selector for the element to click.
            timeout_ms: Maximum time to wait for element in milliseconds.

        Raises:
            WebViewElementNotFoundError: If element is not found within timeout.
            WebViewOperationError: If click operation fails.

        Example:
            >>> controller.click("#submit-btn")
            >>> controller.click("button.primary", timeout_ms=5000)
        """
        self._adapter.click(selector, timeout_ms=timeout_ms)

    def type_text(self, selector: str, text: str, timeout_ms: int = 30000) -> None:
        """Fill an input field with text.

        Args:
            selector: CSS selector for the input element.
            text: Text to type into the element.
            timeout_ms: Maximum time to wait for element in milliseconds.

        Raises:
            WebViewElementNotFoundError: If element is not found within timeout.
            WebViewOperationError: If type operation fails.

        Example:
            >>> controller.type_text("#username", "admin")
            >>> controller.type_text("input[name='email']", "test@example.com")
        """
        self._adapter.type_text(selector, text, timeout_ms=timeout_ms)

    def get_text(self, selector: str, timeout_ms: int = 30000) -> str:
        """Get the text content of an element.

        Args:
            selector: CSS selector for the element.
            timeout_ms: Maximum time to wait for element in milliseconds.

        Returns:
            Text content of the element, or empty string if none.

        Raises:
            WebViewElementNotFoundError: If element is not found within timeout.
            WebViewOperationError: If get text operation fails.

        Example:
            >>> text = controller.get_text(".error-message")
            >>> print(text)
        """
        return self._adapter.get_text(selector, timeout_ms=timeout_ms)

    def get_attribute(self, selector: str, attr: str, timeout_ms: int = 30000) -> str:
        """Get an attribute value of an element.

        Args:
            selector: CSS selector for the element.
            attr: Name of the attribute to retrieve.
            timeout_ms: Maximum time to wait for element in milliseconds.

        Returns:
            Attribute value, or empty string if attribute not found.

        Raises:
            WebViewElementNotFoundError: If element is not found within timeout.
            WebViewOperationError: If get attribute operation fails.

        Example:
            >>> href = controller.get_attribute("a", "href")
            >>> src = controller.get_attribute("img.logo", "src")
        """
        return self._adapter.get_attribute(selector, attr, timeout_ms=timeout_ms)

    def screenshot(
        self,
        path: str,
        selector: str | None = None,
        timeout_ms: int = 30000,
    ) -> bytes:
        """Take a screenshot of the page or element.

        Args:
            path: File path where screenshot will be saved.
            selector: Optional CSS selector to screenshot specific element.
                If None, captures full page.
            timeout_ms: Maximum time to wait for element in milliseconds.

        Returns:
            Screenshot image bytes.

        Raises:
            WebViewElementNotFoundError: If selector specified but element not found.
            WebViewOperationError: If screenshot operation fails.

        Example:
            >>> controller.screenshot("/tmp/page.png")
            >>> controller.screenshot("/tmp/button.png", selector="#submit-btn")
        """
        return self._adapter.screenshot(path, selector=selector, timeout_ms=timeout_ms)

    def execute_script(self, script: str, *args: Any) -> Any:
        """Execute JavaScript in the page context.

        Args:
            script: JavaScript code to execute. Use `return` to get a value.
            *args: Arguments to pass to the script (accessible via `arguments`).

        Returns:
            Result of the script execution, or None if no return value.

        Raises:
            WebViewOperationError: If script execution fails.

        Example:
            >>> title = controller.execute_script("return document.title")
            >>> count = controller.execute_script(
            ...     "return document.querySelectorAll('button').length"
            ... )
        """
        return self._adapter.execute_script(script, *args)

    def wait_for_selector(self, selector: str, timeout_ms: int = 30000) -> bool:
        """Wait for an element to appear in the DOM.

        Args:
            selector: CSS selector to wait for.
            timeout_ms: Maximum time to wait in milliseconds.

        Returns:
            True if element found, False if timeout.

        Example:
            >>> if controller.wait_for_selector(".modal", timeout_ms=5000):
            ...     print("Modal appeared!")
        """
        return self._adapter.wait_for_selector(selector, timeout_ms=timeout_ms)

    def wait_for_load_state(self, state: str, timeout_ms: int = 30000) -> None:
        """Wait for the page to reach a specific load state.

        Args:
            state: Target load state. Options:
                - "loading": Page is still loading
                - "interactive": Page loaded, resources still loading
                - "complete": Page fully loaded
            timeout_ms: Maximum time to wait in milliseconds.

        Raises:
            WebViewOperationError: If state is not supported.
            WebViewTimeoutError: If timeout waiting for state.

        Example:
            >>> controller.wait_for_load_state("complete")
            >>> controller.wait_for_load_state("interactive", timeout_ms=10000)
        """
        expected = state.strip().lower()
        state_order: dict[str, int] = {"loading": 0, "interactive": 1, "complete": 2}
        if expected not in state_order:
            from nomo_e2e.exceptions import WebViewOperationError

            raise WebViewOperationError(f"Unsupported load state: {state}")
        deadline = time.time() + timeout_ms / 1000
        while time.time() < deadline:
            ready_state = str(
                self.execute_script("return document.readyState") or "loading"
            ).lower()
            current_level = state_order.get(ready_state, 0)
            expected_level = state_order[expected]
            if current_level >= expected_level:
                return
            time.sleep(0.2)
        from nomo_e2e.exceptions import WebViewTimeoutError

        raise WebViewTimeoutError(f"Timeout waiting for load state: expected={state}")

    @property
    def title(self) -> str:
        """Get the current page title.

        Returns:
            Page title string.

        Example:
            >>> print(controller.title)
        """
        return self._adapter.title

    @property
    def url(self) -> str:
        """Get the current page URL.

        Returns:
            Page URL string.

        Example:
            >>> print(controller.url)
        """
        return self._adapter.url

    def diagnostics_snapshot(self) -> dict[str, Any]:
        """Get a diagnostic snapshot of the current page state.

        Returns:
            Dictionary containing:
                - driver: Current driver type
                - url: Current page URL
                - title: Current page title
                - ready_state: Document ready state

        Example:
            >>> snapshot = controller.diagnostics_snapshot()
            >>> print(snapshot["ready_state"])
        """
        return {
            "driver": self._driver,
            "url": self.url,
            "title": self.title,
            "ready_state": self.execute_script("return document.readyState"),
        }

    def disconnect(self) -> None:
        """Disconnect from the WebView and cleanup resources.

        Example:
            >>> controller.disconnect()
        """
        self._adapter.disconnect()

    def hover(self, selector: str, timeout_ms: int = 30000) -> None:
        """Hover over an element.

        Args:
            selector: CSS selector string.
            timeout_ms: Timeout in milliseconds.

        Example:
            >>> controller.hover("#dropdown")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.hover(selector, timeout_ms=timeout_ms)

    def dblclick(self, selector: str, timeout_ms: int = 30000) -> None:
        """Double-click an element.

        Args:
            selector: CSS selector string.
            timeout_ms: Timeout in milliseconds.

        Example:
            >>> controller.dblclick("#item")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.dblclick(selector, timeout_ms=timeout_ms)

    def press_key(self, key: str) -> None:
        """Press a keyboard key.

        Args:
            key: Key name (e.g., "Enter", "Escape", "Control+a").

        Example:
            >>> controller.press_key("Enter")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.keyboard.press(key)

    def key_down(self, key: str) -> None:
        """Press and hold a keyboard key.

        Args:
            key: Key name.

        Example:
            >>> controller.key_down("Control")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.keyboard.down(key)

    def key_up(self, key: str) -> None:
        """Release a keyboard key.

        Args:
            key: Key name.

        Example:
            >>> controller.key_up("Control")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.keyboard.up(key)

    def select_option(self, selector: str, value: str) -> None:
        """Select an option from a dropdown.

        Args:
            selector: CSS selector for the select element.
            value: Value to select.

        Example:
            >>> controller.select_option("select#country", "US")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.select_option(selector, value)

    def check(self, selector: str) -> None:
        """Check a checkbox or radio button.

        Args:
            selector: CSS selector string.

        Example:
            >>> controller.check("#agree")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.check(selector)

    def uncheck(self, selector: str) -> None:
        """Uncheck a checkbox.

        Args:
            selector: CSS selector string.

        Example:
            >>> controller.uncheck("#notify")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.uncheck(selector)

    def mouse_move(self, x: int, y: int) -> None:
        """Move mouse to coordinates.

        Args:
            x: X coordinate.
            y: Y coordinate.

        Example:
            >>> controller.mouse_move(100, 200)
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.mouse.move(x, y)

    def mouse_down(self, x: int | None = None, y: int | None = None, button: str = "left") -> None:
        """Press mouse button down.

        Args:
            x: Optional X coordinate.
            y: Optional Y coordinate.
            button: Mouse button (left, right, middle).

        Example:
            >>> controller.mouse_down(100, 200)
            >>> controller.mouse_down(button="right")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        if x is not None and y is not None:
            page.mouse.move(x, y)
        page.mouse.down(button=button)

    def mouse_up(self, button: str = "left") -> None:
        """Release mouse button.

        Args:
            button: Mouse button (left, right, middle).

        Example:
            >>> controller.mouse_up()
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.mouse.up(button=button)

    def mouse_wheel(self, delta_x: int = 0, delta_y: int = 0) -> None:
        """Scroll mouse wheel.

        Args:
            delta_x: Horizontal scroll amount.
            delta_y: Vertical scroll amount.

        Example:
            >>> controller.mouse_wheel(0, -300)
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.mouse.wheel(delta_x, delta_y)

    def upload_file(self, selector: str, file_path: str) -> None:
        """Upload a file to an input element.

        Args:
            selector: CSS selector for the file input.
            file_path: Path to the file to upload.

        Example:
            >>> controller.upload_file("input[type=file]", "./document.pdf")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        page.set_input_files(selector, file_path)

    def drag_and_drop(self, source_selector: str, target_selector: str) -> None:
        """Drag an element to a target location.

        Args:
            source_selector: CSS selector for the source element.
            target_selector: CSS selector for the target element.

        Example:
            >>> controller.drag_and_drop("#source", "#target")
        """
        page = self._get_page()
        if page is None:
            raise WebViewOperationError("Page not available for this adapter type")
        source = page.locator(source_selector)
        target = page.locator(target_selector)
        source.drag_to(target)

    def _get_page(self) -> Any | None:
        """Get the Playwright Page object if available.

        Returns:
            Page object or None if not available (e.g., WebDriver adapter).
        """
        if hasattr(self._adapter, "_page"):
            return self._adapter._page
        return None

    def navigate(self, url: str) -> None:
        """Navigate to a URL.

        Args:
            url: Target URL to navigate to.

        Raises:
            WebViewOperationError: If navigation fails.

        Example:
            >>> controller.navigate("https://example.com")
        """
        if hasattr(self._adapter, "navigate"):
            self._adapter.navigate(url)
        else:
            from nomo_e2e.exceptions import WebViewOperationError

            raise WebViewOperationError("Navigation not supported by this adapter")

    def reload_page(self) -> None:
        """Reload the current page.

        Raises:
            WebViewOperationError: If reload fails.

        Example:
            >>> controller.reload_page()
        """
        if hasattr(self._adapter, "reload"):
            self._adapter.reload()
        else:
            from nomo_e2e.exceptions import WebViewOperationError

            raise WebViewOperationError("Reload not supported by this adapter")

    def go_back(self) -> None:
        """Navigate back in browser history.

        Raises:
            WebViewOperationError: If navigation fails.

        Example:
            >>> controller.go_back()
        """
        if hasattr(self._adapter, "go_back"):
            self._adapter.go_back()
        else:
            from nomo_e2e.exceptions import WebViewOperationError

            raise WebViewOperationError("Back navigation not supported by this adapter")

    def go_forward(self) -> None:
        """Navigate forward in browser history.

        Raises:
            WebViewOperationError: If navigation fails.

        Example:
            >>> controller.go_forward()
        """
        if hasattr(self._adapter, "go_forward"):
            self._adapter.go_forward()
        else:
            from nomo_e2e.exceptions import WebViewOperationError

            raise WebViewOperationError("Forward navigation not supported by this adapter")
