"""Presa API client."""

from __future__ import annotations

import json
import os
import urllib.error
import urllib.request
from dataclasses import asdict
from typing import Any

from presa.errors import ApiError, AuthenticationError, PresaError, TimeoutError
from presa.types import CustomFormat, GenerateOptions, GenerateResult, PresaConfig


class Presa:
    """Client for the Presa HTML-to-PDF API."""

    def __init__(self, config: PresaConfig | None = None) -> None:
        cfg = config or PresaConfig()
        api_key = cfg.api_key or os.environ.get("PRESA_API_KEY")
        if not api_key:
            raise AuthenticationError()
        self._api_key = api_key
        self._base_url = cfg.base_url.rstrip("/")
        self._timeout = cfg.timeout

    def generate(self, options: GenerateOptions) -> GenerateResult:
        """Generate a PDF from HTML content."""
        url = f"{self._base_url}/v1/pdf/from-html"
        body = _build_request_body(options)

        request = urllib.request.Request(
            url,
            data=json.dumps(body).encode("utf-8"),
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {self._api_key}",
            },
            method="POST",
        )

        try:
            with urllib.request.urlopen(request, timeout=self._timeout) as response:
                pdf_bytes = response.read()
                return GenerateResult(buffer=pdf_bytes, size=len(pdf_bytes))
        except urllib.error.HTTPError as exc:
            if exc.code == 401:
                raise AuthenticationError(
                    "Invalid API key. Check your PRESA_API_KEY value."
                ) from exc

            error_body: dict[str, Any] | None = None
            try:
                error_body = json.loads(exc.read().decode("utf-8"))
            except (json.JSONDecodeError, UnicodeDecodeError):
                pass

            if error_body and "error" in error_body:
                err = error_body["error"]
                raise ApiError(
                    code=err.get("code", "UNKNOWN"),
                    message=err.get("message", "Unknown error"),
                    status=err.get("status", exc.code),
                ) from exc

            raise PresaError(
                f"API request failed with status {exc.code}"
            ) from exc
        except TimeoutError:
            raise
        except urllib.error.URLError as exc:
            if "timed out" in str(exc.reason):
                raise TimeoutError(self._timeout) from exc
            raise PresaError(f"Network error: {exc.reason}") from exc


def _build_request_body(options: GenerateOptions) -> dict[str, Any]:
    body: dict[str, Any] = {"html": options.html}
    if options.format is not None:
        if isinstance(options.format, CustomFormat):
            body["format"] = asdict(options.format)
        else:
            body["format"] = options.format
    if options.landscape is not None:
        body["landscape"] = options.landscape
    if options.margins is not None:
        margins = {k: v for k, v in asdict(options.margins).items() if v is not None}
        if margins:
            body["margins"] = margins
    if options.print_background is not None:
        body["printBackground"] = options.print_background
    if options.scale is not None:
        body["scale"] = options.scale
    return body
