from __future__ import annotations

import os
from typing import Any

import httpx

from ._exceptions import (
    APIError,
    AuthenticationError,
    BadRequestError,
    ConnectionError,
    QuotaExceededError,
    RateLimitError,
    TimeoutError,
)
from ._models import ScanResult
from ._version import __version__

_DEFAULT_BASE_URL = "https://api.pinpout.dev"
_DEFAULT_TIMEOUT = 30.0


def _resolve_api_key(api_key: str | None) -> str:
    key = api_key or os.environ.get("PINPOUT_API_KEY")
    if not key:
        raise ValueError(
            "No API key provided. Pass api_key= or set the PINPOUT_API_KEY "
            "environment variable."
        )
    return key


def _resolve_base_url(base_url: str | None) -> str:
    return (base_url or os.environ.get("PINPOUT_BASE_URL") or _DEFAULT_BASE_URL).rstrip("/")


def _handle_error(response: httpx.Response) -> None:
    status = response.status_code

    try:
        body: dict[str, Any] = response.json()
    except Exception:
        body = {}

    message = body.get("detail", response.text or f"HTTP {status}")

    if status == 401:
        raise AuthenticationError(message)
    if status == 429:
        retry_after_raw = response.headers.get("Retry-After")
        retry_after = float(retry_after_raw) if retry_after_raw else None
        raise RateLimitError(message, retry_after=retry_after)
    if status == 403:
        raise QuotaExceededError(message)
    if status == 400:
        raise BadRequestError(message)
    if status >= 500:
        raise APIError(message, status_code=status)

    # Catch-all for other 4xx
    raise APIError(message, status_code=status)


class Pinpout:
    """Synchronous Pinpout client."""

    def __init__(
        self,
        api_key: str | None = None,
        *,
        base_url: str | None = None,
        timeout: float = _DEFAULT_TIMEOUT,
    ) -> None:
        self._api_key = _resolve_api_key(api_key)
        self._base_url = _resolve_base_url(base_url)
        self._client = httpx.Client(
            base_url=self._base_url,
            headers={
                "X-API-Key": self._api_key,
                "User-Agent": f"pinpout-python/{__version__}",
            },
            timeout=timeout,
        )

    def scan(self, text: str, *, return_normalized: bool = False) -> ScanResult:
        payload: dict[str, Any] = {"text": text}
        if return_normalized:
            payload["options"] = {"return_normalized": True}

        try:
            response = self._client.post("/v1/scan", json=payload)
        except httpx.ConnectError as exc:
            raise ConnectionError(str(exc)) from exc
        except httpx.TimeoutException as exc:
            raise TimeoutError(str(exc)) from exc

        if response.status_code != 200:
            _handle_error(response)

        return ScanResult.model_validate(response.json())

    def close(self) -> None:
        self._client.close()

    def __enter__(self) -> Pinpout:
        return self

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


class AsyncPinpout:
    """Asynchronous Pinpout client."""

    def __init__(
        self,
        api_key: str | None = None,
        *,
        base_url: str | None = None,
        timeout: float = _DEFAULT_TIMEOUT,
    ) -> None:
        self._api_key = _resolve_api_key(api_key)
        self._base_url = _resolve_base_url(base_url)
        self._client = httpx.AsyncClient(
            base_url=self._base_url,
            headers={
                "X-API-Key": self._api_key,
                "User-Agent": f"pinpout-python/{__version__}",
            },
            timeout=timeout,
        )

    async def scan(self, text: str, *, return_normalized: bool = False) -> ScanResult:
        payload: dict[str, Any] = {"text": text}
        if return_normalized:
            payload["options"] = {"return_normalized": True}

        try:
            response = await self._client.post("/v1/scan", json=payload)
        except httpx.ConnectError as exc:
            raise ConnectionError(str(exc)) from exc
        except httpx.TimeoutException as exc:
            raise TimeoutError(str(exc)) from exc

        if response.status_code != 200:
            _handle_error(response)

        return ScanResult.model_validate(response.json())

    async def close(self) -> None:
        await self._client.aclose()

    async def __aenter__(self) -> AsyncPinpout:
        return self

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