"""HTTP clients for the Contract Attestation API."""

from __future__ import annotations

from typing import Any

import httpx

from .errors import (
    APIError,
    AuthenticationError,
    BadRequestError,
    ConnectionError,
    PaymentRequiredError,
    RateLimitError,
    ServiceUnavailableError,
    TimeoutError,
    parse_error_detail,
)
from .models import HealthResult, ReputationResult, ReviewResult, StatsResult, StatusResult

_DEFAULT_BASE_URL = "https://code-review-validator.fly.dev"


def _extract_remaining_reviews(headers: httpx.Headers) -> int | None:
    value = headers.get("X-API-Key-Remaining") or headers.get("X-Free-Tier-Remaining")
    if value is None:
        return None
    try:
        return int(value)
    except ValueError:
        return None


def _retry_after(headers: httpx.Headers) -> int | None:
    raw = headers.get("Retry-After")
    if raw is None:
        return None
    try:
        return int(raw)
    except ValueError:
        return None


def _raise_for_response(response: httpx.Response) -> None:
    if response.status_code < 400:
        return

    try:
        payload = response.json()
    except ValueError:
        payload = None

    fallback = response.text.strip() or "Request failed"
    detail = parse_error_detail(payload, fallback)
    status_code = response.status_code

    if status_code == 400:
        raise BadRequestError(status_code, detail, response)
    if status_code == 401:
        raise AuthenticationError(status_code, detail, response)
    if status_code == 402:
        raise PaymentRequiredError(status_code, detail, response)
    if status_code == 429:
        raise RateLimitError(
            status_code,
            detail,
            response,
            retry_after=_retry_after(response.headers),
        )
    if status_code == 503:
        raise ServiceUnavailableError(status_code, detail, response)
    if status_code == 504:
        raise TimeoutError(detail)

    raise APIError(status_code, detail, response)


class AsyncClient:
    """Async SDK client."""

    def __init__(
        self,
        base_url: str = _DEFAULT_BASE_URL,
        api_key: str | None = None,
        timeout: float = 120.0,
        _client: httpx.AsyncClient | None = None,
    ) -> None:
        self._owns_client = _client is None
        if _client is None:
            headers = {"X-API-Key": api_key} if api_key else None
            self._client = httpx.AsyncClient(
                base_url=base_url.rstrip("/"),
                timeout=timeout,
                headers=headers,
            )
        else:
            self._client = _client
            if api_key:
                self._client.headers.setdefault("X-API-Key", api_key)

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

    async def __aexit__(self, exc_type: object, exc: object, tb: object) -> None:
        await self.close()

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

    async def _request(self, method: str, path: str, *, json: dict[str, Any] | None = None) -> httpx.Response:
        try:
            response = await self._client.request(method, path, json=json)
        except httpx.TimeoutException as exc:
            raise TimeoutError(str(exc) or "Request timed out") from exc
        except httpx.RequestError as exc:
            raise ConnectionError(str(exc) or "Request failed") from exc

        _raise_for_response(response)
        return response

    async def review(
        self,
        code: str,
        *,
        language: str = "solidity",
        review_type: str = "security",
        context: str = "",
        encryption_key: str | None = None,
    ) -> ReviewResult:
        payload: dict[str, Any] = {
            "code": code,
            "language": language,
            "reviewType": review_type,
            "context": context,
        }
        if encryption_key is not None:
            payload["encryptionKey"] = encryption_key

        response = await self._request("POST", "/review/", json=payload)
        result = ReviewResult.model_validate(response.json())
        return result.model_copy(update={"remaining_reviews": _extract_remaining_reviews(response.headers)})

    async def status(self, request_hash: str) -> StatusResult:
        response = await self._request("GET", f"/status/{request_hash}")
        return StatusResult.model_validate(response.json())

    async def reputation(self, agent_id: int) -> ReputationResult:
        response = await self._request("GET", f"/reputation/{agent_id}")
        return ReputationResult.model_validate(response.json())

    async def health(self) -> HealthResult:
        response = await self._request("GET", "/health")
        return HealthResult.model_validate(response.json())

    async def stats(self) -> StatsResult:
        response = await self._request("GET", "/stats/")
        return StatsResult.model_validate(response.json())


class SyncClient:
    """Synchronous SDK client."""

    def __init__(
        self,
        base_url: str = _DEFAULT_BASE_URL,
        api_key: str | None = None,
        timeout: float = 120.0,
        _client: httpx.Client | None = None,
    ) -> None:
        self._owns_client = _client is None
        if _client is None:
            headers = {"X-API-Key": api_key} if api_key else None
            self._client = httpx.Client(
                base_url=base_url.rstrip("/"),
                timeout=timeout,
                headers=headers,
            )
        else:
            self._client = _client
            if api_key:
                self._client.headers.setdefault("X-API-Key", api_key)

    def __enter__(self) -> SyncClient:
        return self

    def __exit__(self, exc_type: object, exc: object, tb: object) -> None:
        self.close()

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

    def _request(self, method: str, path: str, *, json: dict[str, Any] | None = None) -> httpx.Response:
        try:
            response = self._client.request(method, path, json=json)
        except httpx.TimeoutException as exc:
            raise TimeoutError(str(exc) or "Request timed out") from exc
        except httpx.RequestError as exc:
            raise ConnectionError(str(exc) or "Request failed") from exc

        _raise_for_response(response)
        return response

    def review(
        self,
        code: str,
        *,
        language: str = "solidity",
        review_type: str = "security",
        context: str = "",
        encryption_key: str | None = None,
    ) -> ReviewResult:
        payload: dict[str, Any] = {
            "code": code,
            "language": language,
            "reviewType": review_type,
            "context": context,
        }
        if encryption_key is not None:
            payload["encryptionKey"] = encryption_key

        response = self._request("POST", "/review/", json=payload)
        result = ReviewResult.model_validate(response.json())
        return result.model_copy(update={"remaining_reviews": _extract_remaining_reviews(response.headers)})

    def status(self, request_hash: str) -> StatusResult:
        response = self._request("GET", f"/status/{request_hash}")
        return StatusResult.model_validate(response.json())

    def reputation(self, agent_id: int) -> ReputationResult:
        response = self._request("GET", f"/reputation/{agent_id}")
        return ReputationResult.model_validate(response.json())

    def health(self) -> HealthResult:
        response = self._request("GET", "/health")
        return HealthResult.model_validate(response.json())

    def stats(self) -> StatsResult:
        response = self._request("GET", "/stats/")
        return StatsResult.model_validate(response.json())
