"""CDP adapter using Playwright connect_over_cdp."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from playwright.sync_api import Browser, BrowserContext, Page, Playwright, sync_playwright

from nomo_e2e.adapters.base import BaseAdapter
from nomo_e2e.exceptions import (
    WebViewConnectionError,
    WebViewElementNotFoundError,
    WebViewOperationError,
)


class CdpAdapter(BaseAdapter):
    """Adapter for CDP-based WebView control using Playwright.

    Uses Playwright's `connect_over_cdp` method to connect to WebViews
    that expose Chrome DevTools Protocol (CDP) endpoint.

    Example:
        >>> from nomo_e2e.adapters.cdp import CdpAdapter
        >>>
        >>> adapter = CdpAdapter("http://localhost:9222")
        >>> adapter.connect()
        >>> adapter.click("#submit-btn")
        >>> adapter.disconnect()
    """

    def __init__(
        self,
        endpoint: str,
        playwright: Playwright | None = None,
        browser: Browser | None = None,
    ) -> None:
        """Initialize CDP adapter.

        Args:
            endpoint: CDP endpoint URL (e.g., "http://localhost:9222").
            playwright: Optional Playwright instance for reuse.
            browser: Optional existing Browser instance.
        """
        self._endpoint = endpoint.rstrip("/")
        self._playwright = playwright
        self._browser = browser
        self._owns_browser = browser is None
        self._context: BrowserContext | None = None
        self._page: Page | None = None

    @property
    def endpoint(self) -> str:
        """Get the CDP endpoint URL."""
        return self._endpoint

    @property
    def is_connected(self) -> bool:
        """Check if currently connected to a WebView."""
        return self._browser is not None and self._page is not None

    def connect(self) -> None:
        """Establish CDP connection to the WebView.

        Raises:
            WebViewConnectionError: If connection fails or no page available.
        """
        if self.is_connected:
            return
        if self._playwright is None:
            self._playwright = sync_playwright().start()
        try:
            self._browser = self._playwright.chromium.connect_over_cdp(self._endpoint)
        except Exception as e:
            raise WebViewConnectionError(f"CDP connection failed: {e}") from e
        if not self._browser.contexts:
            raise WebViewConnectionError("No browser context available after CDP connection")
        self._context = self._browser.contexts[0]
        if not self._context.pages:
            raise WebViewConnectionError("No page available in browser context")
        self._page = self._context.pages[0]

    def disconnect(self) -> None:
        """Disconnect from WebView and cleanup resources."""
        if self._browser and self._owns_browser:
            self._browser.close()
        self._browser = None
        self._context = None
        self._page = None
        if self._playwright:
            self._playwright.stop()
            self._playwright = None

    def _ensure_page(self) -> Page:
        """Ensure page is available.

        Returns:
            Active Page instance.

        Raises:
            WebViewConnectionError: If not connected.
        """
        if self._page is None:
            raise WebViewConnectionError("CDP session not established")
        return self._page

    def _timeout_seconds(self, timeout_ms: int) -> float:
        """Convert milliseconds to seconds."""
        return timeout_ms / 1000

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

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

        Returns:
            True if element found, False if timeout.
        """
        page = self._ensure_page()
        try:
            page.wait_for_selector(
                selector,
                timeout=self._timeout_seconds(timeout_ms),
                state="attached",
            )
            return True
        except Exception:
            return False

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

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

        Raises:
            WebViewElementNotFoundError: If element not found.
            WebViewOperationError: If click fails.
        """
        page = self._ensure_page()
        try:
            page.wait_for_selector(
                selector,
                timeout=self._timeout_seconds(timeout_ms),
                state="attached",
            )
            page.click(selector, timeout=timeout_ms)
        except Exception as e:
            if "Timeout" in str(e) or "not found" in str(e).lower():
                raise WebViewElementNotFoundError(f"Element not found: {selector}") from e
            raise WebViewOperationError(f"Click failed: {e}") from e

    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.
            timeout_ms: Maximum wait time in milliseconds.

        Raises:
            WebViewElementNotFoundError: If element not found.
            WebViewOperationError: If type operation fails.
        """
        page = self._ensure_page()
        try:
            page.wait_for_selector(
                selector,
                timeout=self._timeout_seconds(timeout_ms),
                state="attached",
            )
            page.fill(selector, text, timeout=timeout_ms)
        except Exception as e:
            if "Timeout" in str(e) or "not found" in str(e).lower():
                raise WebViewElementNotFoundError(f"Element not found: {selector}") from e
            raise WebViewOperationError(f"Type text failed: {e}") from e

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

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

        Returns:
            Text content of the element.

        Raises:
            WebViewElementNotFoundError: If element not found.
            WebViewOperationError: If get text fails.
        """
        page = self._ensure_page()
        try:
            page.wait_for_selector(
                selector,
                timeout=self._timeout_seconds(timeout_ms),
                state="attached",
            )
            return page.locator(selector).text_content(timeout=timeout_ms) or ""
        except Exception as e:
            if "Timeout" in str(e) or "not found" in str(e).lower():
                raise WebViewElementNotFoundError(f"Element not found: {selector}") from e
            raise WebViewOperationError(f"Get text failed: {e}") from e

    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.
            timeout_ms: Maximum wait time in milliseconds.

        Returns:
            Attribute value.

        Raises:
            WebViewElementNotFoundError: If element not found.
            WebViewOperationError: If get attribute fails.
        """
        page = self._ensure_page()
        try:
            page.wait_for_selector(
                selector,
                timeout=self._timeout_seconds(timeout_ms),
                state="attached",
            )
            return page.locator(selector).get_attribute(attr, timeout=timeout_ms) or ""
        except Exception as e:
            if "Timeout" in str(e) or "not found" in str(e).lower():
                raise WebViewElementNotFoundError(f"Element not found: {selector}") from e
            raise WebViewOperationError(f"Get attribute failed: {e}") from e

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

        Args:
            script: JavaScript code to execute.
            *args: Arguments to pass to the script.

        Returns:
            Result of script execution.

        Raises:
            WebViewOperationError: If script execution fails.
        """
        page = self._ensure_page()
        try:
            if args:
                args_list = list(args)
                wrapped = f"function() {{ const _args = {args_list}; {script} }}"
                return page.evaluate(wrapped)
            return page.evaluate(f"() => {{ {script} }}")
        except Exception as e:
            raise WebViewOperationError(f"Execute script failed: {e}") from e

    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 to save screenshot.
            selector: Optional CSS selector for element screenshot.
            timeout_ms: Maximum wait time in milliseconds.

        Returns:
            Screenshot image bytes.

        Raises:
            WebViewElementNotFoundError: If selector specified but element not found.
            WebViewOperationError: If screenshot fails.
        """
        page = self._ensure_page()
        try:
            if selector:
                page.wait_for_selector(
                    selector,
                    timeout=self._timeout_seconds(timeout_ms),
                    state="attached",
                )
                element = page.locator(selector)
                image_bytes = element.screenshot(timeout=timeout_ms)
            else:
                image_bytes = page.screenshot(timeout=timeout_ms)
            output_path = Path(path)
            output_path.parent.mkdir(parents=True, exist_ok=True)
            output_path.write_bytes(image_bytes)
            return image_bytes
        except Exception as e:
            if "Timeout" in str(e) or "not found" in str(e).lower():
                raise WebViewElementNotFoundError(
                    f"Element not found for screenshot: {selector}"
                ) from e
            raise WebViewOperationError(f"Screenshot failed: {e}") from e

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

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