"""Browser manager singleton for Camoufox."""

from __future__ import annotations

import asyncio
from typing import TYPE_CHECKING, Any

from camoufox.async_api import AsyncCamoufox

from .config import BrowserConfig, config
from .exceptions import BrowserNotInitializedError, PageNotFoundError
from .types import ConsoleMessage, NetworkRequest, TabInfo

if TYPE_CHECKING:
    from playwright.async_api import Page


class BrowserManager:
    """Singleton manager for the Camoufox browser instance.

    Implements lazy initialization - browser is only started on first tool call.
    """

    def __init__(self) -> None:
        self._browser: Any | None = None
        self._context: Any | None = None
        self._pages: list[Page] = []
        self._active_page_index: int = 0
        self._config: BrowserConfig = config
        self._console_messages: list[ConsoleMessage] = []
        self._network_requests: list[NetworkRequest] = []
        self._pending_dialog: Any | None = None
        self._lock = asyncio.Lock()

    async def _ensure_browser(self) -> None:
        """Ensure browser is initialized."""
        async with self._lock:
            if self._browser is None:
                launch_kwargs = self._config.to_launch_kwargs()
                self._browser = await AsyncCamoufox(**launch_kwargs).__aenter__()

    async def get_page(self) -> Page:
        """Get the current active page, creating one if none exists."""
        await self._ensure_browser()

        if not self._pages:
            page = await self._browser.new_page()
            self._setup_page_handlers(page)
            self._pages.append(page)
            self._active_page_index = 0

        return self._pages[self._active_page_index]

    def _setup_page_handlers(self, page: Page) -> None:
        """Set up event handlers for a page."""

        def on_console(msg: Any) -> None:
            self._console_messages.append(
                ConsoleMessage(
                    type=msg.type,
                    text=msg.text,
                    location=f"{msg.location.get('url', '')}:{msg.location.get('lineNumber', '')}"
                    if msg.location
                    else None,
                )
            )

        def on_request(request: Any) -> None:
            self._network_requests.append(
                NetworkRequest(
                    url=request.url,
                    method=request.method,
                    status=None,
                    resource_type=request.resource_type,
                )
            )

        def on_response(response: Any) -> None:
            # Update the corresponding request with status
            for req in reversed(self._network_requests):
                if req.url == response.url and req.status is None:
                    req.status = response.status
                    break

        def on_dialog(dialog: Any) -> None:
            self._pending_dialog = dialog

        page.on("console", on_console)
        page.on("request", on_request)
        page.on("response", on_response)
        page.on("dialog", on_dialog)

    async def create_page(self) -> tuple[Page, int]:
        """Create a new page/tab and return it with its index."""
        await self._ensure_browser()

        page = await self._browser.new_page()
        self._setup_page_handlers(page)
        self._pages.append(page)
        index = len(self._pages) - 1
        self._active_page_index = index
        return page, index

    async def close_page(self, index: int | None = None) -> None:
        """Close a page by index. If index is None, closes the active page."""
        if not self._pages:
            raise BrowserNotInitializedError("No pages are open")

        if index is None:
            index = self._active_page_index

        if index < 0 or index >= len(self._pages):
            raise PageNotFoundError(index, len(self._pages))

        page = self._pages.pop(index)
        await page.close()

        # Adjust active page index
        if self._pages:
            if self._active_page_index >= len(self._pages):
                self._active_page_index = len(self._pages) - 1
        else:
            self._active_page_index = 0

    async def select_page(self, index: int) -> Page:
        """Select a page by index and make it active."""
        if not self._pages:
            raise BrowserNotInitializedError("No pages are open")

        if index < 0 or index >= len(self._pages):
            raise PageNotFoundError(index, len(self._pages))

        self._active_page_index = index
        return self._pages[index]

    async def list_pages(self) -> list[TabInfo]:
        """List all open pages/tabs."""
        tabs = []
        for i, page in enumerate(self._pages):
            tabs.append(
                TabInfo(
                    index=i,
                    url=page.url,
                    title=await page.title(),
                    is_active=i == self._active_page_index,
                )
            )
        return tabs

    def get_console_messages(self, level: str = "info") -> list[ConsoleMessage]:
        """Get console messages filtered by level."""
        level_order = ["debug", "info", "warning", "error"]
        min_level = level_order.index(level) if level in level_order else 1

        return [
            msg
            for msg in self._console_messages
            if level_order.index(msg.type) >= min_level
            if msg.type in level_order
        ]

    def get_network_requests(self, include_static: bool = False) -> list[NetworkRequest]:
        """Get network requests, optionally filtering out static resources."""
        if include_static:
            return list(self._network_requests)

        static_types = {"image", "stylesheet", "font", "media"}
        return [req for req in self._network_requests if req.resource_type not in static_types]

    def clear_console_messages(self) -> None:
        """Clear stored console messages."""
        self._console_messages.clear()

    def clear_network_requests(self) -> None:
        """Clear stored network requests."""
        self._network_requests.clear()

    async def get_pending_dialog(self) -> Any | None:
        """Get the pending dialog if any."""
        return self._pending_dialog

    async def handle_dialog(self, accept: bool, prompt_text: str | None = None) -> bool:
        """Handle the pending dialog."""
        if self._pending_dialog is None:
            return False

        dialog = self._pending_dialog
        self._pending_dialog = None

        if accept:
            if prompt_text is not None:
                await dialog.accept(prompt_text)
            else:
                await dialog.accept()
        else:
            await dialog.dismiss()

        return True

    async def close(self) -> None:
        """Close the browser and clean up resources."""
        async with self._lock:
            if self._browser is not None:
                try:
                    await self._browser.__aexit__(None, None, None)
                except Exception:
                    pass
                self._browser = None
                self._pages.clear()
                self._active_page_index = 0
                self._console_messages.clear()
                self._network_requests.clear()
                self._pending_dialog = None

    @property
    def is_initialized(self) -> bool:
        """Check if browser is initialized."""
        return self._browser is not None

    def update_config(self, **kwargs: Any) -> None:
        """Update browser configuration. Only takes effect before browser is launched."""
        for key, value in kwargs.items():
            if hasattr(self._config, key):
                setattr(self._config, key, value)


# Global singleton instance
browser_manager = BrowserManager()
