"""
Browser Automation Tool

Playwright-based stealth browser control.
Provides navigation, clicking, typing, screenshots, and network sniffing.
"""

import asyncio
import base64
import json
import logging
import os
import threading
from typing import Dict, Any, Optional, List

from .base import BaseTool, ToolMetadata, ToolParameter, ToolPermission, ToolExecutionContext, ToolResult

logger = logging.getLogger(__name__)

# Lazy-loaded Playwright
_browser_sessions: Dict[str, Any] = {}
_sessions_lock = threading.Lock()


class BrowserSession:
    """Manages a Playwright browser session with anti-detection."""

    def __init__(self):
        self._browser = None
        self._context = None
        self._page = None
        self._loop = None
        self._thread = None

    def _ensure_loop(self):
        """Start a dedicated event loop thread for Playwright."""
        if self._loop and self._loop.is_running():
            return
        self._loop = asyncio.new_event_loop()
        self._thread = threading.Thread(target=self._loop.run_forever, daemon=True)
        self._thread.start()

    def _run(self, coro, timeout=120):
        """Run async coroutine on the session's event loop."""
        self._ensure_loop()
        future = asyncio.run_coroutine_threadsafe(coro, self._loop)
        return future.result(timeout=timeout)

    def launch(self, workspace_path: str, headless: bool = True, profile: str = "default"):
        """Launch a browser session."""
        async def _launch():
            from playwright.async_api import async_playwright

            self._pw = await async_playwright().start()
            profile_dir = os.path.join(workspace_path, ".browser_profiles", profile)
            os.makedirs(profile_dir, exist_ok=True)

            self._browser = await self._pw.chromium.launch_persistent_context(
                user_data_dir=profile_dir,
                headless=headless,
                args=[
                    "--disable-blink-features=AutomationControlled",
                    "--no-sandbox",
                ],
                viewport={"width": 1280, "height": 800},
                user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
            )
            self._page = self._browser.pages[0] if self._browser.pages else await self._browser.new_page()

        self._run(_launch())
        return "Browser launched"

    def navigate(self, url: str):
        """Navigate to a URL."""
        async def _nav():
            await self._page.goto(url, wait_until="domcontentloaded", timeout=30000)
            await self._page.wait_for_timeout(1000)
            return await self._get_snapshot()
        return self._run(_nav())

    def click(self, selector: str = None, text: str = None):
        """Click an element."""
        async def _click():
            if text:
                el = self._page.get_by_text(text, exact=False).first
                await el.click(timeout=10000)
            elif selector:
                await self._page.click(selector, timeout=10000)
            await self._page.wait_for_timeout(500)
            return await self._get_snapshot()
        return self._run(_click())

    def type_text(self, selector: str, text: str, press_enter: bool = False):
        """Type text into an input."""
        async def _type():
            await self._page.fill(selector, text, timeout=10000)
            if press_enter:
                await self._page.press(selector, "Enter")
            await self._page.wait_for_timeout(500)
            return await self._get_snapshot()
        return self._run(_type())

    def screenshot(self, workspace_path: str) -> dict:
        """Take a screenshot."""
        async def _screenshot():
            screenshot_dir = os.path.join(workspace_path, ".screenshots")
            os.makedirs(screenshot_dir, exist_ok=True)
            import time
            path = os.path.join(screenshot_dir, f"screenshot_{int(time.time())}.png")
            buf = await self._page.screenshot(full_page=False)
            with open(path, "wb") as f:
                f.write(buf)
            return {
                "path": path,
                "base64": base64.b64encode(buf).decode("utf-8"),
            }
        return self._run(_screenshot())

    def extract(self, selector: str = None, max_length: int = 5000):
        """Extract text from page."""
        async def _extract():
            if selector:
                el = await self._page.query_selector(selector)
                text = await el.inner_text() if el else "Element not found"
            else:
                text = await self._page.inner_text("body")
            return text[:max_length]
        return self._run(_extract())

    def scroll(self, direction: str = "down", amount: int = 500):
        """Scroll the page."""
        async def _scroll():
            delta = amount if direction == "down" else -amount
            await self._page.evaluate(f"window.scrollBy(0, {delta})")
            await self._page.wait_for_timeout(300)
            return await self._get_snapshot()
        return self._run(_scroll())

    def execute_js(self, script: str):
        """Execute JavaScript."""
        async def _js():
            result = await self._page.evaluate(script)
            return str(result) if result is not None else "(no return value)"
        return self._run(_js())

    def go_back(self):
        """Navigate back."""
        async def _back():
            await self._page.go_back()
            await self._page.wait_for_timeout(1000)
            return await self._get_snapshot()
        return self._run(_back())

    def close(self):
        """Close the browser."""
        async def _close():
            if self._browser:
                await self._browser.close()
            if hasattr(self, "_pw") and self._pw:
                await self._pw.stop()
        try:
            self._run(_close(), timeout=10)
        except Exception:
            pass

    async def _get_snapshot(self) -> str:
        """Get page title, URL, and interactive elements summary."""
        title = await self._page.title()
        url = self._page.url
        # Get interactive elements
        elements = await self._page.evaluate("""
            () => {
                const els = document.querySelectorAll('a, button, input, select, textarea, [role="button"]');
                return Array.from(els).slice(0, 30).map((el, i) => {
                    const tag = el.tagName.toLowerCase();
                    const text = (el.innerText || el.value || el.placeholder || '').trim().substring(0, 50);
                    const type = el.type || '';
                    return `[${i}] <${tag}${type ? ' type='+type : ''}> ${text}`;
                });
            }
        """)
        parts = [f"Page: {title}", f"URL: {url}", "", "Interactive elements:"]
        parts.extend(elements)
        return "\n".join(parts)


class BrowserTool(BaseTool):
    """Control a stealth browser for web automation."""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="BROWSER",
            description="Control a stealth browser. Actions: launch, navigate, click, type, screenshot, extract, scroll, execute_js, go_back, close.",
            category="browser",
            parameters=[
                ToolParameter("action", "string", "Browser action", required=True,
                              enum=["launch", "navigate", "click", "type", "screenshot",
                                    "extract", "scroll", "execute_js", "go_back", "close"]),
                ToolParameter("url", "string", "URL for navigate"),
                ToolParameter("selector", "string", "CSS selector"),
                ToolParameter("text", "string", "Text for type/click"),
                ToolParameter("press_enter", "boolean", "Press Enter after typing", default=False),
                ToolParameter("script", "string", "JavaScript for execute_js"),
                ToolParameter("direction", "string", "Scroll direction (up/down)", default="down"),
                ToolParameter("session_id", "string", "Browser session ID", default="default"),
                ToolParameter("headless", "boolean", "Run headless", default=True),
            ],
            permissions=[ToolPermission.NETWORK, ToolPermission.EXECUTE],
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        action = params.get("action", "")
        session_id = params.get("session_id", "default")

        try:
            if action == "launch":
                session = BrowserSession()
                with _sessions_lock:
                    _browser_sessions[session_id] = session
                result = session.launch(
                    context.workspace_path,
                    headless=params.get("headless", True),
                    profile=session_id,
                )
                return ToolResult.ok(result)

            # All other actions need an existing session
            with _sessions_lock:
                session = _browser_sessions.get(session_id)
            if not session:
                return ToolResult.fail(f"No browser session '{session_id}'. Use action='launch' first.")

            if action == "navigate":
                return ToolResult.ok(session.navigate(params.get("url", "")))
            elif action == "click":
                return ToolResult.ok(session.click(params.get("selector"), params.get("text")))
            elif action == "type":
                return ToolResult.ok(session.type_text(
                    params.get("selector", ""), params.get("text", ""),
                    params.get("press_enter", False)))
            elif action == "screenshot":
                data = session.screenshot(context.workspace_path)
                return ToolResult(success=True, output=f"Screenshot saved: {data['path']}",
                                  base64_image=data["base64"])
            elif action == "extract":
                return ToolResult.ok(session.extract(params.get("selector")))
            elif action == "scroll":
                return ToolResult.ok(session.scroll(params.get("direction", "down")))
            elif action == "execute_js":
                return ToolResult.ok(session.execute_js(params.get("script", "")))
            elif action == "go_back":
                return ToolResult.ok(session.go_back())
            elif action == "close":
                session.close()
                with _sessions_lock:
                    _browser_sessions.pop(session_id, None)
                return ToolResult.ok("Browser closed")
            else:
                return ToolResult.fail(f"Unknown action: {action}")

        except Exception as e:
            logger.exception(f"Browser error: {e}")
            return ToolResult.fail(f"Browser error: {e}")


def get_all_tools():
    return [BrowserTool]
