"""Browser automation core module."""

import asyncio
from typing import Any, Optional

from playwright.async_api import Browser as PWBrowser
from playwright.async_api import BrowserContext, Page, async_playwright

from .config import Config
from .extractor import smart_extract
from .exporter import auto_export
from .llm import LLMRouter
from .stealth import (
    CloudflareBypass,
    FingerprintRandomizer,
    StealthConfig,
    get_random_user_agent,
    get_random_viewport,
    get_stealth_args,
    get_stealth_scripts,
)


class Browser:
    """
    High-level browser automation interface.

    Usage:
        async with Browser() as browser:
            await browser.goto("https://example.com")
            data = await browser.extract("title")
    """

    def __init__(
        self,
        headless: bool = True,
        model: Optional[str] = None,
        config: Optional[Config] = None,
        proxy: Optional[str] = None,
        stealth: bool = True,
        stealth_config: Optional[StealthConfig] = None,
    ):
        self.headless = headless
        self.model = model
        self.config = config or Config.load()
        self.llm_router = LLMRouter(self.config)
        self.proxy = proxy
        self.stealth = stealth
        self.stealth_config = stealth_config or StealthConfig()

        self._playwright: Optional[Any] = None
        self._browser: Optional[PWBrowser] = None
        self._context: Optional[BrowserContext] = None
        self._page: Optional[Page] = None

    async def __aenter__(self) -> "Browser":
        """Async context manager entry."""
        await self.launch()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """Async context manager exit."""
        await self.close()

    async def launch(self) -> "Browser":
        """Launch the browser."""
        self._playwright = await async_playwright().start()

        # Prepare launch options
        launch_options = {
            "headless": self.headless,
            "args": get_stealth_args() if self.stealth else [
                "--disable-blink-features=AutomationControlled",
                "--disable-web-security",
                "--disable-features=IsolateOrigins,site-per-process",
            ]
        }

        # Add proxy if specified
        if self.proxy:
            launch_options["proxy"] = {"server": self.proxy}

        # Try to use existing Chrome/Chromium if available
        try:
            self._browser = await self._playwright.chromium.launch(**launch_options)
        except Exception as e:
            print(f"Note: Could not launch system Chrome ({e})")
            print("Installing Chromium...")
            # Will download Chromium if not present
            self._browser = await self._playwright.chromium.launch(**launch_options)

        # Prepare context options with stealth
        if self.stealth:
            fingerprint = FingerprintRandomizer.get_random_fingerprint()
            context_options = {
                "viewport": fingerprint["viewport"],
                "user_agent": fingerprint["user_agent"],
                "device_scale_factor": fingerprint["pixel_ratio"],
            }
        else:
            context_options = {
                "viewport": {"width": 1920, "height": 1080},
                "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
            }

        # Add proxy to context if specified
        if self.proxy:
            context_options["proxy"] = {"server": self.proxy}

        self._context = await self._browser.new_context(**context_options)
        self._page = await self._context.new_page()

        # Inject stealth scripts
        if self.stealth:
            scripts = get_stealth_scripts(self.stealth_config)
            for script in scripts:
                await self._page.add_init_script(script)
        else:
            # Basic stealth only
            await self._page.add_init_script("""
                Object.defineProperty(navigator, 'webdriver', {
                    get: () => undefined
                });
                Object.defineProperty(navigator, 'plugins', {
                    get: () => [1, 2, 3, 4, 5]
                });
            """)

        return self

    async def close(self) -> None:
        """Close the browser and cleanup."""
        if self._page:
            await self._page.close()
            self._page = None

        if self._context:
            await self._context.close()
            self._context = None

        if self._browser:
            await self._browser.close()
            self._browser = None

        if self._playwright:
            await self._playwright.stop()
            self._playwright = None

        await self.llm_router.close_all()

    async def goto(self, url: str, wait_until: str = "networkidle") -> "Browser":
        """
        Navigate to a URL.

        Args:
            url: URL to navigate to
            wait_until: When to consider navigation complete

        Returns:
            Self for chaining
        """
        if not self._page:
            raise RuntimeError("Browser not launched. Use 'async with Browser()' or call launch()")

        # Add https:// if no protocol specified
        if not url.startswith(("http://", "https://")):
            url = "https://" + url

        await self._page.goto(url, wait_until=wait_until)
        
        # Check for Cloudflare challenge
        if self.stealth:
            content = await self._page.content()
            if CloudflareBypass.is_cloudflare_page(content):
                await CloudflareBypass.wait_for_challenge(self._page)
        
        return self

    async def extract(self, selector_or_instruction: str) -> str:
        """
        Extract data from the current page.

        Args:
            selector_or_instruction: CSS selector or natural language instruction

        Returns:
            Extracted text content
        """
        if not self._page:
            raise RuntimeError("Browser not launched")

        # Try as CSS selector first
        if selector_or_instruction.startswith((".", "#", "[", ">", " ", "h1", "h2", "h3", "div", "p", "a", "span", "button", "input")):
            try:
                element = await self._page.query_selector(selector_or_instruction)
                if element:
                    return await element.inner_text() or ""
                return ""
            except Exception:
                pass

        # Use LLM for natural language extraction (only if configured)
        try:
            html_content = await self._page.content()
            provider = self.llm_router.get_provider(self.model)
            return await provider.extract_from_page(html_content, selector_or_instruction)
        except ValueError:
            # LLM not configured, return empty
            return ""

    async def click(self, selector: str) -> "Browser":
        """
        Click an element.

        Args:
            selector: CSS selector for the element

        Returns:
            Self for chaining
        """
        if not self._page:
            raise RuntimeError("Browser not launched")

        await self._page.click(selector)
        return self

    async def type(self, selector: str, text: str) -> "Browser":
        """
        Type text into an input field.

        Args:
            selector: CSS selector for the input
            text: Text to type

        Returns:
            Self for chaining
        """
        if not self._page:
            raise RuntimeError("Browser not launched")

        await self._page.fill(selector, text)
        return self

    async def screenshot(self, path: Optional[str] = None, full_page: bool = True) -> dict:
        """
        Take a screenshot of the current page.

        Args:
            path: File path to save screenshot. If None, saves to default location.
            full_page: Whether to capture full page or just viewport

        Returns:
            Dictionary with screenshot info:
            {
                "success": bool,
                "path": str,
                "size_kb": float,
                "page_title": str,
                "page_url": str,
                "timestamp": str,
                "full_page": bool
            }
        """
        if not self._page:
            raise RuntimeError("Browser not launched")

        from datetime import datetime
        import os

        # Generate timestamp
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

        # Default screenshot directory: /mnt/fnos/SurfAI/项目文件/截图
        if path is None:
            default_dir = "/mnt/fnos/SurfAI/项目文件/截图"
            os.makedirs(default_dir, exist_ok=True)
            path = f"{default_dir}/screenshot_{timestamp}.png"

        # Get page info before screenshot
        page_title = await self.get_title()
        page_url = await self.get_url()

        # Take screenshot
        await self._page.screenshot(path=path, full_page=full_page)

        # Verify file exists and get size
        if os.path.exists(path):
            size_bytes = os.path.getsize(path)
            size_kb = round(size_bytes / 1024, 2)
            success = True
        else:
            size_kb = 0
            success = False

        # Return detailed info
        return {
            "success": success,
            "path": path,
            "size_kb": size_kb,
            "page_title": page_title,
            "page_url": page_url,
            "timestamp": timestamp,
            "full_page": full_page
        }

    async def get_url(self) -> str:
        """Get current page URL."""
        if not self._page:
            raise RuntimeError("Browser not launched")
        return self._page.url

    async def get_title(self) -> str:
        """Get current page title."""
        if not self._page:
            raise RuntimeError("Browser not launched")
        return await self._page.title()

    async def wait(self, seconds: float) -> "Browser":
        """
        Wait for specified seconds.

        Args:
            seconds: Time to wait

        Returns:
            Self for chaining
        """
        await asyncio.sleep(seconds)
        return self

    async def scroll(self, direction: str = "down", amount: int = 500) -> "Browser":
        """
        Scroll the page.

        Args:
            direction: 'up' or 'down'
            amount: Pixels to scroll

        Returns:
            Self for chaining
        """
        if not self._page:
            raise RuntimeError("Browser not launched")

        if direction == "down":
            await self._page.evaluate(f"window.scrollBy(0, {amount})")
        else:
            await self._page.evaluate(f"window.scrollBy(0, -{amount})")

        return self

    async def execute_task(self, instruction: str) -> dict[str, Any]:
        """
        Execute a natural language task.

        Args:
            instruction: Natural language instruction

        Returns:
            Results including extracted data and screenshot path
        """
        # Try to get provider, fallback to local if not configured
        try:
            provider = self.llm_router.get_provider(self.model)
        except ValueError:
            # No model configured, use local parser
            from .llm.local_parser import LocalIntentParser
            plan = LocalIntentParser.parse(instruction)
        else:
            plan = await provider.understand(instruction)

        results = {
            "actions_executed": [],
            "extracted_data": [],
            "screenshots": [],
        }

        for action in plan.actions:
            action_type = action.get("type")

            if action_type == "goto":
                await self.goto(action["url"])
                results["actions_executed"].append(f"goto: {action['url']}")

            elif action_type == "click":
                await self.click(action["selector"])
                results["actions_executed"].append(f"click: {action['selector']}")

            elif action_type == "type":
                await self.type(action["selector"], action["text"])
                results["actions_executed"].append(f"type: {action['selector']}")

            elif action_type == "extract":
                data = await self.extract(action["selector"])
                results["extracted_data"].append(data)
                results["actions_executed"].append(f"extract: {action['selector']}")

            elif action_type == "screenshot":
                screenshot_info = await self.screenshot(action.get("path"))
                results["screenshots"].append(screenshot_info)
                results["actions_executed"].append(f"screenshot: {screenshot_info['path']} ({screenshot_info['size_kb']} KB)")

            elif action_type == "wait":
                await self.wait(action.get("seconds", 1))
                results["actions_executed"].append(f"wait: {action.get('seconds', 1)}s")

        return results

    async def smart_extract(self, instruction: str) -> Any:
        """
        Smart extraction based on natural language instruction.

        Args:
            instruction: What to extract (e.g., "all links", "table data", "prices")

        Returns:
            Extracted data in appropriate format
        """
        if not self._page:
            raise RuntimeError("Browser not launched")
        return await smart_extract(self._page, instruction)

    async def export(self, data: Any, filepath: str) -> str:
        """
        Export data to file (auto-detects format from extension).

        Args:
            data: Data to export
            filepath: Output file path (.json, .csv, .txt)

        Returns:
            Path to exported file
        """
        path = auto_export(data, filepath)
        return str(path)
