"""
ScreenshotSage Python SDK

Capture high-quality screenshots of any website.

Example:
    >>> from screenshotsage import ScreenshotSage
    >>> client = ScreenshotSage(api_key="your-api-key")
    >>> result = client.capture(url="https://example.com")
    >>> print(result["data"]["download_url"])

    # Async usage
    >>> import asyncio
    >>> async def main():
    ...     async with ScreenshotSage(api_key="your-api-key") as client:
    ...         result = await client.capture_async(url="https://example.com")
    ...         print(result["data"]["download_url"])
    >>> asyncio.run(main())
"""

from __future__ import annotations

from typing import Any, Literal, TypedDict, Optional, List
import httpx

__version__ = "1.0.0"
__all__ = ["ScreenshotSage", "ScreenshotSageError", "ScreenshotOptions", "ScreenshotResponse"]


class ClipRegion(TypedDict, total=False):
    """Region to clip from the page."""
    x: int
    y: int
    width: int
    height: int


class Geolocation(TypedDict, total=False):
    """Geolocation emulation settings."""
    latitude: float
    longitude: float
    accuracy: float


class BasicAuth(TypedDict):
    """HTTP Basic Authentication credentials."""
    username: str
    password: str


class Cookie(TypedDict, total=False):
    """Cookie to set before navigation."""
    name: str
    value: str
    domain: str
    path: str
    expires: int
    httpOnly: bool
    secure: bool
    sameSite: Literal["Strict", "Lax", "None"]


class ScreenshotOptions(TypedDict, total=False):
    """Options for capturing a screenshot."""
    url: str
    format: Literal["png", "jpeg", "webp", "pdf", "html"]
    width: int
    height: int
    fullPage: bool
    clip: ClipRegion
    deviceScaleFactor: Literal["1", "2", "3"]
    quality: int
    delay: int
    waitForSelector: str
    userAgent: str
    darkMode: bool
    devicePreset: Literal["desktop", "tablet", "mobile", "custom"]
    geolocation: Geolocation
    timezone: str
    language: str
    basicAuth: BasicAuth
    cookies: List[Cookie]
    blockAds: bool
    blockCookieBanners: bool
    blockChatWidgets: bool
    hideSelectors: List[str]
    customCss: str
    customJs: str
    webhookUrl: str


class Dimensions(TypedDict):
    """Screenshot dimensions."""
    width: int
    height: int


class ScreenshotData(TypedDict):
    """Screenshot response data."""
    id: int
    url: str
    downloadUrl: str
    format: str
    fileSize: str
    dimensions: Dimensions
    generatedAt: str


class ScreenshotResponse(TypedDict, total=False):
    """Response from screenshot API."""
    success: bool
    data: ScreenshotData
    error: str


class ScreenshotSageError(Exception):
    """Exception raised for ScreenshotSage API errors."""

    def __init__(
        self,
        message: str,
        status_code: Optional[int] = None,
        response: Optional[ScreenshotResponse] = None,
    ):
        super().__init__(message)
        self.status_code = status_code
        self.response = response


class ScreenshotSage:
    """
    ScreenshotSage SDK Client.

    Args:
        api_key: Your ScreenshotSage API key.
        base_url: Base URL for the API (default: https://screenshotsage.com).
        timeout: Request timeout in seconds (default: 60).

    Example:
        >>> client = ScreenshotSage(api_key="your-api-key")
        >>> result = client.capture(url="https://example.com")
        >>> print(result["data"]["download_url"])

        # Mobile screenshot
        >>> result = client.capture(
        ...     url="https://example.com",
        ...     device_preset="mobile",
        ...     format="webp"
        ... )
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://screenshotsage.com",
        timeout: float = 60.0,
    ):
        if not api_key:
            raise ValueError("API key is required")

        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self._sync_client: Optional[httpx.Client] = None
        self._async_client: Optional[httpx.AsyncClient] = None

    def _get_headers(self) -> dict[str, str]:
        return {
            "Content-Type": "application/json",
            "X-Api-Key": self.api_key,
        }

    def _get_sync_client(self) -> httpx.Client:
        if self._sync_client is None:
            self._sync_client = httpx.Client(
                base_url=self.base_url,
                headers=self._get_headers(),
                timeout=self.timeout,
            )
        return self._sync_client

    def _get_async_client(self) -> httpx.AsyncClient:
        if self._async_client is None:
            self._async_client = httpx.AsyncClient(
                base_url=self.base_url,
                headers=self._get_headers(),
                timeout=self.timeout,
            )
        return self._async_client

    def close(self) -> None:
        """Close the HTTP client connections."""
        if self._sync_client is not None:
            self._sync_client.close()
            self._sync_client = None

    async def aclose(self) -> None:
        """Close the async HTTP client connections."""
        if self._async_client is not None:
            await self._async_client.aclose()
            self._async_client = None

    def __enter__(self) -> "ScreenshotSage":
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    async def __aenter__(self) -> "ScreenshotSage":
        return self

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

    def _handle_response(self, response: httpx.Response) -> ScreenshotResponse:
        data: ScreenshotResponse = response.json()

        if not response.is_success or not data.get("success"):
            raise ScreenshotSageError(
                data.get("error") or f"Request failed with status {response.status_code}",
                status_code=response.status_code,
                response=data,
            )

        return data

    def capture(self, **options: Any) -> ScreenshotResponse:
        """
        Capture a screenshot of a URL (synchronous).

        Args:
            url: The URL to capture (required).
            format: Output format - png, jpeg, webp, pdf, or html.
            width: Viewport width in pixels (320-3840).
            height: Viewport height in pixels (200-3840).
            fullPage: Capture the entire scrollable page.
            clip: Capture a specific region {x, y, width, height}.
            deviceScaleFactor: Device scale factor - "1", "2", or "3".
            quality: Image quality for JPEG/WebP (1-100).
            delay: Delay before capture in milliseconds (0-10000).
            waitForSelector: CSS selector to wait for before capture.
            userAgent: Custom user agent string.
            darkMode: Force dark color scheme.
            devicePreset: Device preset - desktop, tablet, mobile, or custom.
            geolocation: Emulate location {latitude, longitude, accuracy?}.
            timezone: IANA timezone identifier (e.g., "America/New_York").
            language: Accept-Language header value (e.g., "en-US").
            basicAuth: HTTP Basic Auth {username, password}.
            cookies: Cookies to set before navigation.
            blockAds: Block advertisements (Starter+ plans).
            blockCookieBanners: Block GDPR/cookie consent banners (Starter+ plans).
            blockChatWidgets: Hide live chat widgets (Starter+ plans).
            hideSelectors: Custom CSS selectors to hide.
            customCss: Custom CSS to inject (Professional+ plans).
            customJs: Custom JavaScript to execute (Professional+ plans).
            webhookUrl: Webhook URL for completion notification (Professional+ plans).

        Returns:
            Screenshot response with download URL.

        Raises:
            ScreenshotSageError: If the request fails.
            ValueError: If URL is not provided.
        """
        if not options.get("url"):
            raise ScreenshotSageError("URL is required")

        client = self._get_sync_client()
        response = client.post("/api/screenshot", json=options)
        return self._handle_response(response)

    async def capture_async(self, **options: Any) -> ScreenshotResponse:
        """
        Capture a screenshot of a URL (asynchronous).

        See capture() for full argument documentation.
        """
        if not options.get("url"):
            raise ScreenshotSageError("URL is required")

        client = self._get_async_client()
        response = await client.post("/api/screenshot", json=options)
        return self._handle_response(response)

    def download(self, download_url: str) -> bytes:
        """
        Download screenshot as bytes (synchronous).

        Args:
            download_url: The download URL from capture response.

        Returns:
            Screenshot file bytes.
        """
        url = download_url if download_url.startswith("http") else f"{self.base_url}{download_url}"
        client = self._get_sync_client()
        response = client.get(url)

        if not response.is_success:
            raise ScreenshotSageError(f"Download failed with status {response.status_code}")

        return response.content

    async def download_async(self, download_url: str) -> bytes:
        """
        Download screenshot as bytes (asynchronous).

        Args:
            download_url: The download URL from capture response.

        Returns:
            Screenshot file bytes.
        """
        url = download_url if download_url.startswith("http") else f"{self.base_url}{download_url}"
        client = self._get_async_client()
        response = await client.get(url)

        if not response.is_success:
            raise ScreenshotSageError(f"Download failed with status {response.status_code}")

        return response.content

    def capture_and_download(self, **options: Any) -> bytes:
        """
        Capture and download in one step (synchronous).

        See capture() for full argument documentation.

        Returns:
            Screenshot file bytes.
        """
        result = self.capture(**options)
        download_url = result.get("data", {}).get("downloadUrl")

        if not download_url:
            raise ScreenshotSageError("No download URL in response")

        return self.download(download_url)

    async def capture_and_download_async(self, **options: Any) -> bytes:
        """
        Capture and download in one step (asynchronous).

        See capture() for full argument documentation.

        Returns:
            Screenshot file bytes.
        """
        result = await self.capture_async(**options)
        download_url = result.get("data", {}).get("downloadUrl")

        if not download_url:
            raise ScreenshotSageError("No download URL in response")

        return await self.download_async(download_url)

    def get(self, screenshot_id: int) -> ScreenshotResponse:
        """
        Get screenshot metadata by ID (synchronous).

        Args:
            screenshot_id: Screenshot ID.

        Returns:
            Screenshot metadata.
        """
        client = self._get_sync_client()
        response = client.get(f"/api/screenshot/{screenshot_id}")
        return self._handle_response(response)

    async def get_async(self, screenshot_id: int) -> ScreenshotResponse:
        """
        Get screenshot metadata by ID (asynchronous).

        Args:
            screenshot_id: Screenshot ID.

        Returns:
            Screenshot metadata.
        """
        client = self._get_async_client()
        response = await client.get(f"/api/screenshot/{screenshot_id}")
        return self._handle_response(response)
