"""Pixwell API client."""

from typing import Any, Dict, Optional, Union

import httpx

from .exceptions import (
    AuthenticationError,
    CaptureError,
    NetworkError,
    PixwellError,
    RateLimitError,
    ValidationError,
)
from .types import (
    BatchOptions,
    BatchResponse,
    ScreenshotOptions,
    ScreenshotResponse,
    UsageResponse,
)

DEFAULT_BASE_URL = "https://api.pixwell.dev"
DEFAULT_TIMEOUT = 60.0


class Pixwell:
    """
    Synchronous Pixwell API client for capturing screenshots.

    Example:
        >>> from pixwell import Pixwell
        >>>
        >>> client = Pixwell(api_key="your-api-key")
        >>>
        >>> # Capture a screenshot
        >>> screenshot = client.screenshot(
        ...     url="https://example.com",
        ...     width=1920,
        ...     height=1080,
        ...     format="png"
        ... )
        >>>
        >>> # Save to file
        >>> with open("screenshot.png", "wb") as f:
        ...     f.write(screenshot.data)
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
    ) -> None:
        """
        Initialize the Pixwell client.

        Args:
            api_key: Your Pixwell API key.
            base_url: API base URL (default: https://api.pixwell.dev).
            timeout: Request timeout in seconds (default: 60).
        """
        if not api_key:
            raise ValidationError("API key is required", field="api_key")

        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._client = httpx.Client(
            timeout=timeout,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "User-Agent": "pixwell-python/1.0.0",
            },
        )

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

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

    def close(self) -> None:
        """Close the HTTP client."""
        self._client.close()

    def screenshot(
        self,
        url: str,
        *,
        width: int = 1280,
        height: int = 720,
        full_page: bool = False,
        format: str = "png",
        quality: int = 80,
        mobile: bool = False,
        dark_mode: bool = False,
        delay: int = 0,
        selector: Optional[str] = None,
        cache_ttl: int = 0,
    ) -> ScreenshotResponse:
        """
        Capture a screenshot of a webpage.

        Args:
            url: URL to capture.
            width: Viewport width in pixels (default: 1280).
            height: Viewport height in pixels (default: 720).
            full_page: Capture full scrollable page (default: False).
            format: Image format - 'png', 'jpeg', or 'webp' (default: 'png').
            quality: Image quality 1-100 (default: 80).
            mobile: Emulate mobile device (default: False).
            dark_mode: Enable dark mode (default: False).
            delay: Wait time in ms before capture (default: 0, max: 10000).
            selector: CSS selector to capture specific element.
            cache_ttl: Cache TTL in seconds (default: 0, max: 3600).

        Returns:
            ScreenshotResponse with image data and metadata.

        Raises:
            ValidationError: If parameters are invalid.
            AuthenticationError: If API key is invalid.
            RateLimitError: If rate limit is exceeded.
            CaptureError: If screenshot capture fails.
        """
        options = ScreenshotOptions(
            url=url,
            width=width,
            height=height,
            full_page=full_page,
            format=format,  # type: ignore
            quality=quality,
            mobile=mobile,
            dark_mode=dark_mode,
            delay=delay,
            selector=selector,
            cache_ttl=cache_ttl,
        )

        response = self._request("POST", "/api/v1/screenshot", json=options.to_dict())

        return ScreenshotResponse(
            data=response.content,
            content_type=response.headers.get("content-type", "application/octet-stream"),
            size=len(response.content),
            duration_ms=int(response.headers.get("x-duration-ms", "0")),
            cached=response.headers.get("x-cache") == "HIT",
        )

    def batch(
        self,
        urls: list[str],
        *,
        width: int = 1280,
        height: int = 720,
        full_page: bool = False,
        format: str = "png",
        quality: int = 80,
        mobile: bool = False,
        dark_mode: bool = False,
        delay: int = 0,
    ) -> BatchResponse:
        """
        Capture multiple screenshots in a single request.

        Args:
            urls: List of URLs to capture (max 10).
            width: Viewport width in pixels (default: 1280).
            height: Viewport height in pixels (default: 720).
            full_page: Capture full scrollable page (default: False).
            format: Image format - 'png', 'jpeg', or 'webp' (default: 'png').
            quality: Image quality 1-100 (default: 80).
            mobile: Emulate mobile device (default: False).
            dark_mode: Enable dark mode (default: False).
            delay: Wait time in ms before capture (default: 0, max: 10000).

        Returns:
            BatchResponse with results for each URL.

        Raises:
            ValidationError: If parameters are invalid.
            AuthenticationError: If API key is invalid.
            RateLimitError: If rate limit is exceeded.
        """
        options = BatchOptions(
            urls=urls,
            width=width,
            height=height,
            full_page=full_page,
            format=format,  # type: ignore
            quality=quality,
            mobile=mobile,
            dark_mode=dark_mode,
            delay=delay,
        )

        response = self._request("POST", "/api/v1/batch", json=options.to_dict())
        return BatchResponse.from_dict(response.json())

    def usage(self) -> UsageResponse:
        """
        Get current usage statistics.

        Returns:
            UsageResponse with daily and monthly limits.
        """
        response = self._request("GET", "/api/v1/usage")
        return UsageResponse.from_dict(response.json())

    def _request(
        self,
        method: str,
        path: str,
        json: Optional[Dict[str, Any]] = None,
    ) -> httpx.Response:
        """Make an authenticated request to the API."""
        url = f"{self._base_url}{path}"

        try:
            response = self._client.request(method, url, json=json)
        except httpx.TimeoutException as e:
            raise NetworkError(f"Request timed out after {self._timeout}s") from e
        except httpx.RequestError as e:
            raise NetworkError(str(e)) from e

        if not response.is_success:
            self._handle_error(response)

        return response

    def _handle_error(self, response: httpx.Response) -> None:
        """Handle error responses from the API."""
        try:
            error_data = response.json()
            message = error_data.get("error", {}).get("message", response.text)
            code = error_data.get("error", {}).get("code", "UNKNOWN_ERROR")
        except Exception:
            message = response.text or f"Request failed with status {response.status_code}"
            code = "UNKNOWN_ERROR"

        if response.status_code == 401:
            raise AuthenticationError(message)
        elif response.status_code == 429:
            retry_after = response.headers.get("retry-after")
            raise RateLimitError(
                message,
                retry_after=int(retry_after) if retry_after else None,
            )
        elif response.status_code == 400:
            raise ValidationError(message)
        elif response.status_code >= 500:
            raise CaptureError(message)
        else:
            raise PixwellError(message, code, response.status_code)


class AsyncPixwell:
    """
    Asynchronous Pixwell API client for capturing screenshots.

    Example:
        >>> import asyncio
        >>> from pixwell import AsyncPixwell
        >>>
        >>> async def main():
        ...     async with AsyncPixwell(api_key="your-api-key") as client:
        ...         screenshot = await client.screenshot(
        ...             url="https://example.com",
        ...             width=1920,
        ...             height=1080
        ...         )
        ...         with open("screenshot.png", "wb") as f:
        ...             f.write(screenshot.data)
        >>>
        >>> asyncio.run(main())
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
    ) -> None:
        """
        Initialize the async Pixwell client.

        Args:
            api_key: Your Pixwell API key.
            base_url: API base URL (default: https://api.pixwell.dev).
            timeout: Request timeout in seconds (default: 60).
        """
        if not api_key:
            raise ValidationError("API key is required", field="api_key")

        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._client = httpx.AsyncClient(
            timeout=timeout,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "User-Agent": "pixwell-python/1.0.0",
            },
        )

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

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

    async def close(self) -> None:
        """Close the HTTP client."""
        await self._client.aclose()

    async def screenshot(
        self,
        url: str,
        *,
        width: int = 1280,
        height: int = 720,
        full_page: bool = False,
        format: str = "png",
        quality: int = 80,
        mobile: bool = False,
        dark_mode: bool = False,
        delay: int = 0,
        selector: Optional[str] = None,
        cache_ttl: int = 0,
    ) -> ScreenshotResponse:
        """Capture a screenshot of a webpage. See Pixwell.screenshot for details."""
        options = ScreenshotOptions(
            url=url,
            width=width,
            height=height,
            full_page=full_page,
            format=format,  # type: ignore
            quality=quality,
            mobile=mobile,
            dark_mode=dark_mode,
            delay=delay,
            selector=selector,
            cache_ttl=cache_ttl,
        )

        response = await self._request("POST", "/api/v1/screenshot", json=options.to_dict())

        return ScreenshotResponse(
            data=response.content,
            content_type=response.headers.get("content-type", "application/octet-stream"),
            size=len(response.content),
            duration_ms=int(response.headers.get("x-duration-ms", "0")),
            cached=response.headers.get("x-cache") == "HIT",
        )

    async def batch(
        self,
        urls: list[str],
        *,
        width: int = 1280,
        height: int = 720,
        full_page: bool = False,
        format: str = "png",
        quality: int = 80,
        mobile: bool = False,
        dark_mode: bool = False,
        delay: int = 0,
    ) -> BatchResponse:
        """Capture multiple screenshots. See Pixwell.batch for details."""
        options = BatchOptions(
            urls=urls,
            width=width,
            height=height,
            full_page=full_page,
            format=format,  # type: ignore
            quality=quality,
            mobile=mobile,
            dark_mode=dark_mode,
            delay=delay,
        )

        response = await self._request("POST", "/api/v1/batch", json=options.to_dict())
        return BatchResponse.from_dict(response.json())

    async def usage(self) -> UsageResponse:
        """Get current usage statistics. See Pixwell.usage for details."""
        response = await self._request("GET", "/api/v1/usage")
        return UsageResponse.from_dict(response.json())

    async def _request(
        self,
        method: str,
        path: str,
        json: Optional[Dict[str, Any]] = None,
    ) -> httpx.Response:
        """Make an authenticated request to the API."""
        url = f"{self._base_url}{path}"

        try:
            response = await self._client.request(method, url, json=json)
        except httpx.TimeoutException as e:
            raise NetworkError(f"Request timed out after {self._timeout}s") from e
        except httpx.RequestError as e:
            raise NetworkError(str(e)) from e

        if not response.is_success:
            self._handle_error(response)

        return response

    def _handle_error(self, response: httpx.Response) -> None:
        """Handle error responses from the API."""
        try:
            error_data = response.json()
            message = error_data.get("error", {}).get("message", response.text)
            code = error_data.get("error", {}).get("code", "UNKNOWN_ERROR")
        except Exception:
            message = response.text or f"Request failed with status {response.status_code}"
            code = "UNKNOWN_ERROR"

        if response.status_code == 401:
            raise AuthenticationError(message)
        elif response.status_code == 429:
            retry_after = response.headers.get("retry-after")
            raise RateLimitError(
                message,
                retry_after=int(retry_after) if retry_after else None,
            )
        elif response.status_code == 400:
            raise ValidationError(message)
        elif response.status_code >= 500:
            raise CaptureError(message)
        else:
            raise PixwellError(message, code, response.status_code)
