from __future__ import annotations

from typing import Any, Mapping, TypeVar

import httpx
from pydantic import BaseModel

from ebay_client.core.auth.oauth import EbayOAuthClient
from ebay_client.core.errors import ResponseDecodingError, TransportError, build_api_error

ModelT = TypeVar("ModelT", bound=BaseModel)


class ApiTransport:
    def __init__(
        self,
        *,
        base_url: str,
        oauth_client: EbayOAuthClient,
        timeout_seconds: float = 30.0,
        default_headers: Mapping[str, str] | None = None,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.oauth_client = oauth_client
        self.timeout_seconds = timeout_seconds
        self.default_headers = dict(default_headers or {})

    def request(
        self,
        method: str,
        path: str,
        *,
        scopes: list[str] | None = None,
        params: Mapping[str, Any] | None = None,
        json_body: Any | None = None,
        headers: Mapping[str, str] | None = None,
        content: bytes | None = None,
    ) -> httpx.Response:
        token = self.oauth_client.get_valid_token(scopes=scopes)
        request_headers = dict(self.default_headers)
        request_headers.update(headers or {})
        request_headers["Authorization"] = f"Bearer {token.access_token}"
        filtered_params = None
        if params is not None:
            filtered_params = {key: value for key, value in params.items() if value is not None}
        url = f"{self.base_url}{path}"
        try:
            with httpx.Client(timeout=self.timeout_seconds) as client:
                response = client.request(
                    method,
                    url,
                    params=filtered_params,
                    json=json_body,
                    headers=request_headers,
                    content=content,
                )
        except httpx.HTTPError as exc:
            raise TransportError(f"HTTP request failed for {method} {path}") from exc

        if response.is_error:
            try:
                payload = response.json()
            except ValueError:
                payload = response.text
            raise build_api_error(response.status_code, payload)
        return response

    def request_json(self, method: str, path: str, **kwargs: Any) -> Any:
        response = self.request(method, path, **kwargs)
        if response.status_code == 204:
            return None
        if not response.content or not response.content.strip():
            return None
        try:
            return response.json()
        except ValueError as exc:
            raise ResponseDecodingError(f"Response for {method} {path} is not valid JSON") from exc

    def request_model(self, model_type: type[ModelT], method: str, path: str, **kwargs: Any) -> ModelT:
        payload = self.request_json(method, path, **kwargs)
        return model_type.model_validate(payload)
