"""Transport abstraction for API clients.

Defines APIResponse (transport-agnostic response) and APIClient (protocol for GET/POST).
Implementations: HttpClient (_http.py) and CLIClient (_cli.py).
"""

from __future__ import annotations

import json as json_module
from dataclasses import dataclass, field
from typing import Any, Protocol, runtime_checkable

from ..errors import InvalidResponse, RequestFailed


@dataclass
class APIResponse:
    """Transport-agnostic API response.

    Wraps response data from any transport (HTTP or CLI subprocess).
    Stores the raw bytes so binary payloads (ZIP archives) survive intact.
    """

    status_code: int
    _body: bytes
    headers: dict[str, str] = field(default_factory=dict)

    @property
    def text(self) -> str:
        """Return response body as text (UTF-8, lossy)."""
        return self._body.decode("utf-8", errors="replace")

    @property
    def content(self) -> bytes:
        """Return response body as raw bytes."""
        return self._body

    def json(self) -> Any:
        """Parse response body as JSON.

        Raises:
            InvalidResponse: If JSON parsing fails.
        """
        try:
            return json_module.loads(self.text)
        except (ValueError, json_module.JSONDecodeError) as e:
            raise InvalidResponse(str(e)) from e

    def raise_for_status(self) -> None:
        """Raise RequestFailed if status code indicates an error."""
        if self.status_code >= 400:
            raise RequestFailed(self.status_code, self.text)


@runtime_checkable
class APIClient(Protocol):
    """Protocol for API clients (HTTP or CLI)."""

    async def get(
        self,
        url: str,
        headers: dict[str, str] | None = None,
        params: dict[str, Any] | None = None,
        timeout: float | None = None,
    ) -> APIResponse: ...

    async def post(
        self,
        url: str,
        headers: dict[str, str] | None = None,
        json: dict[str, Any] | None = None,
    ) -> APIResponse: ...

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