"""WebDriver adapter implemented with Playwright HTTP request context."""

from __future__ import annotations

import base64
import time
from pathlib import Path
from typing import Any

from playwright.sync_api import APIRequestContext, Playwright, sync_playwright

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


def _extract_webdriver_error(payload: dict[str, Any]) -> tuple[str, str]:
    """Extract error information from WebDriver response."""
    value = payload.get("value")
    if not isinstance(value, dict):
        return "unknown error", str(payload)
    error = str(value.get("error", "unknown error"))
    message = str(value.get("message", ""))
    return error, message


def _extract_element_id(raw_element: dict[str, Any]) -> str:
    """Extract element ID from WebDriver element reference."""
    element_key = "element-6066-11e4-a52e-4f735466cecf"
    if element_key in raw_element:
        return str(raw_element[element_key])
    if "ELEMENT" in raw_element:
        return str(raw_element["ELEMENT"])
    if raw_element:
        return str(next(iter(raw_element.values())))
    raise WebViewProtocolError("WebDriver element id missing")


class WebDriverAdapter(BaseAdapter):
    """Adapter for W3C WebDriver endpoint.

    Uses Playwright's HTTP request context to interact with WebDriver endpoints,
    commonly exposed by Tauri applications via tauri-plugin-webdriver.

    Example:
        >>> from nomo_e2e.adapters.webdriver import WebDriverAdapter
        >>>
        >>> adapter = WebDriverAdapter("http://127.0.0.1:4445")
        >>> adapter.connect()
        >>> adapter.click("#submit-btn")
        >>> adapter.disconnect()
    """

    def __init__(
        self,
        endpoint: str,
        request_context: APIRequestContext | None = None,
    ) -> None:
        """Initialize WebDriver adapter.

        Args:
            endpoint: WebDriver endpoint URL (e.g., "http://127.0.0.1:4445").
            request_context: Optional existing Playwright APIRequestContext.
        """
        self._endpoint = endpoint.rstrip("/")
        self._request = request_context
        self._session_id: str | None = None
        self._playwright: Playwright | None = None
        self._owns_request_context = request_context is None

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

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

    def connect(self) -> None:
        """Create a WebDriver session.

        Raises:
            WebViewConnectionError: If session creation fails.
        """
        if self.is_connected:
            return
        if self._request is None:
            self._playwright = sync_playwright().start()
            self._request = self._playwright.request.new_context()
        response = self._request.post(f"{self._endpoint}/session", data={"capabilities": {}})
        if not response.ok:
            raise WebViewConnectionError(
                f"Create session failed: status={response.status}, body={response.text()}"
            )
        payload = response.json()
        value = payload.get("value")
        if isinstance(value, dict) and value.get("sessionId"):
            self._session_id = str(value["sessionId"])
            return
        session_id = payload.get("sessionId")
        if session_id:
            self._session_id = str(session_id)
            return
        raise WebViewConnectionError(f"Session id missing in payload: {payload}")

    def disconnect(self) -> None:
        """Close WebDriver session and cleanup resources."""
        if self._request and self._session_id:
            self._request.delete(f"{self._endpoint}/session/{self._session_id}")
        self._session_id = None
        if self._request and self._owns_request_context:
            self._request.dispose()
            self._request = None
        if self._playwright:
            self._playwright.stop()
            self._playwright = None

    def _session_url(self, path: str) -> str:
        """Build URL for session-scoped requests."""
        if not self._session_id:
            raise WebViewConnectionError("WebDriver session not established")
        return f"{self._endpoint}/session/{self._session_id}{path}"

    def _request_json(
        self,
        method: str,
        path: str,
        data: dict[str, Any] | None = None,
        timeout_ms: int | None = None,
    ) -> dict[str, Any]:
        """Execute HTTP request and parse WebDriver JSON response."""
        if not self._request:
            raise WebViewConnectionError("WebDriver request context not initialized")
        url = self._session_url(path)
        request_kwargs: dict[str, Any] = {}
        if data is not None:
            request_kwargs["data"] = data
        if timeout_ms is not None:
            request_kwargs["timeout"] = timeout_ms
        if method == "GET":
            response = self._request.get(url, **request_kwargs)
        elif method == "POST":
            response = self._request.post(url, **request_kwargs)
        elif method == "DELETE":
            response = self._request.delete(url, **request_kwargs)
        else:
            raise WebViewOperationError(f"Unsupported request method: {method}")
        if not response.ok:
            payload = (
                response.json() if response.text() else {"value": {"message": response.text()}}
            )
            error, message = _extract_webdriver_error(payload)
            if error == "no such element":
                raise WebViewElementNotFoundError(message or "Element not found")
            if error == "timeout":
                raise WebViewTimeoutError(message or "WebDriver timeout")
            raise WebViewProtocolError(
                f"WebDriver request failed: status={response.status}, error={error}, message={message}"
            )
        payload = response.json()
        if "value" in payload and isinstance(payload["value"], dict):
            error = payload["value"].get("error")
            if error:
                err_name, err_message = _extract_webdriver_error(payload)
                if err_name == "no such element":
                    raise WebViewElementNotFoundError(err_message or "Element not found")
                if err_name == "timeout":
                    raise WebViewTimeoutError(err_message or "WebDriver timeout")
                raise WebViewProtocolError(f"WebDriver error: {err_name}: {err_message}")
        return payload

    def find_elements(self, selector: str) -> list[str]:
        """Find elements by CSS selector.

        Args:
            selector: CSS selector string.

        Returns:
            List of element IDs.
        """
        payload = self._request_json(
            "POST",
            "/elements",
            data={"using": "css selector", "value": selector},
        )
        raw_elements = payload.get("value", [])
        if not isinstance(raw_elements, list):
            return []
        return [_extract_element_id(raw) for raw in raw_elements if isinstance(raw, dict)]

    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.
        """
        deadline = time.time() + timeout_ms / 1000
        while time.time() < deadline:
            if self.find_elements(selector):
                return True
            time.sleep(0.2)
        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.
        """
        if not self.wait_for_selector(selector, timeout_ms):
            raise WebViewElementNotFoundError(f"Element not found within timeout: {selector}")
        element_id = self.find_elements(selector)[0]
        self._request_json("POST", f"/element/{element_id}/click", data={}, 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.
            timeout_ms: Maximum wait time in milliseconds.

        Raises:
            WebViewElementNotFoundError: If element not found.
            WebViewOperationError: If type operation fails.
        """
        if not self.wait_for_selector(selector, timeout_ms):
            raise WebViewElementNotFoundError(f"Element not found within timeout: {selector}")
        element_id = self.find_elements(selector)[0]
        self._request_json("POST", f"/element/{element_id}/clear", data={}, timeout_ms=timeout_ms)
        self._request_json(
            "POST",
            f"/element/{element_id}/value",
            data={"text": text, "value": list(text)},
            timeout_ms=timeout_ms,
        )

    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.
        """
        if not self.wait_for_selector(selector, timeout_ms):
            raise WebViewElementNotFoundError(f"Element not found within timeout: {selector}")
        element_id = self.find_elements(selector)[0]
        payload = self._request_json("GET", f"/element/{element_id}/text", timeout_ms=timeout_ms)
        return str(payload.get("value", ""))

    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.
        """
        if not self.wait_for_selector(selector, timeout_ms):
            raise WebViewElementNotFoundError(f"Element not found within timeout: {selector}")
        element_id = self.find_elements(selector)[0]
        payload = self._request_json(
            "GET",
            f"/element/{element_id}/attribute/{attr}",
            timeout_ms=timeout_ms,
        )
        value = payload.get("value")
        return "" if value is None else str(value)

    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.
        """
        payload = self._request_json(
            "POST",
            "/execute/sync",
            data={"script": script, "args": list(args)},
        )
        return payload.get("value")

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

        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.
        """
        route = "/screenshot"
        if selector:
            if not self.wait_for_selector(selector, timeout_ms):
                raise WebViewElementNotFoundError(f"Element not found within timeout: {selector}")
            element_id = self.find_elements(selector)[0]
            route = f"/element/{element_id}/screenshot"
        payload = self._request_json("GET", route, timeout_ms=timeout_ms)
        encoded = payload.get("value")
        if not isinstance(encoded, str):
            raise WebViewProtocolError(f"Invalid screenshot payload: {payload}")
        image_bytes = base64.b64decode(encoded)
        output_path = Path(path)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_bytes(image_bytes)
        return image_bytes

    @property
    def url(self) -> str:
        """Get the current page URL."""
        payload = self._request_json("GET", "/url")
        return str(payload.get("value", ""))

    @property
    def title(self) -> str:
        """Get the current page title."""
        title = self.execute_script("return document.title")
        return "" if title is None else str(title)

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

        Args:
            url: Target URL to navigate to.

        Raises:
            WebViewOperationError: If navigation fails.
        """
        self._request_json("POST", "/url", data={"url": url})

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

        Raises:
            WebViewOperationError: If reload fails.
        """
        self._request_json("POST", "/refresh")

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

        Raises:
            WebViewOperationError: If navigation fails.
        """
        self._request_json("POST", "/back")

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

        Raises:
            WebViewOperationError: If navigation fails.
        """
        self._request_json("POST", "/forward")
