"""
Browser Bridge Client

Thin client for browser automation via the Nowledge Mem extension.
Communicates with the extension through the Mem App REST API bridge.
"""

import asyncio
import base64
import os
from pathlib import Path
from typing import Any, Optional

import httpx

from .types import (
    BridgeCommand,
    BridgeResponse,
    ClickResult,
    FillResult,
    NavigateResult,
    TabsListResult,
)


class BrowserClient:
    """
    Async client for browser automation via the Nowledge Mem extension.

    Uses the Mem App's REST API as a bridge to communicate with the extension.
    """

    def __init__(
        self,
        base_url: str = "http://127.0.0.1:14242",
        api_key: Optional[str] = None,
        timeout: float = 30.0,
    ):
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        token = (api_key or os.environ.get("NOWLEDGE_API_KEY") or os.environ.get("NMEM_API_KEY") or "").strip()
        self._headers = {"Authorization": f"Bearer {token}"} if token else {}
        self._client: Optional[httpx.AsyncClient] = None

    async def __aenter__(self) -> "BrowserClient":
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            timeout=self.timeout,
            headers=self._headers,
        )
        return self

    async def __aexit__(self, *args: Any) -> None:
        if self._client:
            await self._client.aclose()

    @property
    def client(self) -> httpx.AsyncClient:
        if self._client is None:
            self._client = httpx.AsyncClient(
                base_url=self.base_url,
                timeout=self.timeout,
                headers=self._headers,
            )
        return self._client

    async def _send_command(
        self, action: str, params: Optional[dict[str, Any]] = None
    ) -> dict[str, Any]:
        """Send a command to the browser bridge."""
        response = await self.client.post(
            "/api/browser-bridge",
            json={"action": action, "params": params or {}},
        )
        response.raise_for_status()
        return response.json()

    # =========================================================================
    # Navigation
    # =========================================================================

    async def navigate(self, url: str, wait_until: str = "load") -> NavigateResult:
        """Navigate to a URL."""
        result = await self._send_command("navigate", {"url": url, "waitUntil": wait_until})
        return NavigateResult.model_validate(result.get("data", result))

    async def go_back(self) -> bool:
        """Go back in browser history."""
        result = await self._send_command("go_back")
        return result.get("success", False)

    async def go_forward(self) -> bool:
        """Go forward in browser history."""
        result = await self._send_command("go_forward")
        return result.get("success", False)

    async def reload(self) -> bool:
        """Reload the current page."""
        result = await self._send_command("reload")
        return result.get("success", False)

    # =========================================================================
    # Interaction
    # =========================================================================

    async def click(self, selector: str) -> ClickResult:
        """Click an element by CSS selector."""
        result = await self._send_command("click", {"selector": selector})
        return ClickResult.model_validate(result.get("data", result))

    async def fill(
        self, selector: str, text: str, clear: bool = True, submit: bool = False
    ) -> FillResult:
        """Type text into an input field."""
        result = await self._send_command(
            "fill",
            {
                "selector": selector,
                "text": text,
                "clear": clear,
                "submit": submit,
            },
        )
        return FillResult.model_validate(result.get("data", result))

    async def hover(self, selector: str) -> bool:
        """Hover over an element."""
        result = await self._send_command("hover", {"selector": selector})
        return result.get("success", False)

    async def press_key(self, key: str) -> bool:
        """Press a keyboard key."""
        result = await self._send_command("press_key", {"key": key})
        return result.get("success", False)

    async def scroll(self, direction: str, amount: int = 300) -> bool:
        """Scroll the page."""
        result = await self._send_command("scroll", {"direction": direction, "amount": amount})
        return result.get("success", False)

    # =========================================================================
    # Information Extraction
    # =========================================================================

    async def screenshot(
        self,
        selector: Optional[str] = None,
        output_path: Optional[str] = None,
    ) -> dict[str, Any]:
        """Take a screenshot."""
        result = await self._send_command("screenshot", {"selector": selector})

        if result.get("success") and output_path and result.get("data"):
            # Save base64 data to file
            data = result.get("data", "")
            if data:
                Path(output_path).write_bytes(base64.b64decode(data))

        return result

    async def extract(self, selector: str, as_html: bool = False) -> dict[str, Any]:
        """Extract text or HTML from elements."""
        result = await self._send_command("extract", {"selector": selector, "asHtml": as_html})
        return result

    async def get_page_info(self) -> dict[str, Any]:
        """Get current page URL and title."""
        result = await self._send_command("get_page_info")
        return result.get("data", result)

    async def evaluate(self, js_code: str) -> dict[str, Any]:
        """Execute JavaScript in the page context."""
        result = await self._send_command("evaluate", {"code": js_code})
        return result

    # =========================================================================
    # Tab Management
    # =========================================================================

    async def tabs_list(self) -> TabsListResult:
        """List all open tabs."""
        result = await self._send_command("tabs_list")
        return TabsListResult.model_validate(result.get("data", result))

    async def tabs_switch(self, tab_id: int) -> bool:
        """Switch to a specific tab."""
        result = await self._send_command("tabs_switch", {"tabId": tab_id})
        return result.get("success", False)

    # =========================================================================
    # Wait
    # =========================================================================

    async def wait_for_selector(self, selector: str, timeout: int = 30000) -> bool:
        """Wait for an element to appear."""
        result = await self._send_command(
            "wait_for_selector",
            {
                "selector": selector,
                "timeout": timeout,
            },
        )
        return result.get("success", False)


class BrowserClientSync:
    """Synchronous wrapper for BrowserClient."""

    def __init__(
        self,
        base_url: str = "http://127.0.0.1:14242",
        api_key: Optional[str] = None,
        timeout: float = 30.0,
    ):
        self._async_client = BrowserClient(base_url=base_url, api_key=api_key, timeout=timeout)

    def _run(self, coro: Any) -> Any:
        """Run an async coroutine synchronously."""
        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            loop = None

        if loop is not None:
            return asyncio.ensure_future(coro)
        else:
            return asyncio.run(coro)

    # Navigation
    def navigate(self, url: str, wait_until: str = "load") -> NavigateResult:
        return self._run(self._async_client.navigate(url, wait_until))

    def go_back(self) -> bool:
        return self._run(self._async_client.go_back())

    def go_forward(self) -> bool:
        return self._run(self._async_client.go_forward())

    def reload(self) -> bool:
        return self._run(self._async_client.reload())

    # Interaction
    def click(self, selector: str) -> ClickResult:
        return self._run(self._async_client.click(selector))

    def fill(
        self, selector: str, text: str, clear: bool = True, submit: bool = False
    ) -> FillResult:
        return self._run(self._async_client.fill(selector, text, clear, submit))

    def hover(self, selector: str) -> bool:
        return self._run(self._async_client.hover(selector))

    def press_key(self, key: str) -> bool:
        return self._run(self._async_client.press_key(key))

    def scroll(self, direction: str, amount: int = 300) -> bool:
        return self._run(self._async_client.scroll(direction, amount))

    # Information Extraction
    def screenshot(self, selector: Optional[str] = None, output_path: Optional[str] = None) -> dict:
        return self._run(self._async_client.screenshot(selector, output_path))

    def extract(self, selector: str, as_html: bool = False) -> dict:
        return self._run(self._async_client.extract(selector, as_html))

    def get_page_info(self) -> dict:
        return self._run(self._async_client.get_page_info())

    def evaluate(self, js_code: str) -> dict:
        return self._run(self._async_client.evaluate(js_code))

    # Tabs
    def tabs_list(self) -> TabsListResult:
        return self._run(self._async_client.tabs_list())

    def tabs_switch(self, tab_id: int) -> bool:
        return self._run(self._async_client.tabs_switch(tab_id))

    # Wait
    def wait_for_selector(self, selector: str, timeout: int = 30000) -> bool:
        return self._run(self._async_client.wait_for_selector(selector, timeout))
