"""Base class for all MercadoPago API resource modules.

Provides authenticated HTTP helpers that every resource (Payment, Order,
Customer, etc.) inherits.  Handles request-option merging, header
construction, JSON encoding, and delegation to the configured HTTP client.
"""
from json.encoder import JSONEncoder
from urllib.parse import quote

from mercadopago.config.config import Config
from mercadopago.config.request_options import RequestOptions
from mercadopago.errors.response import MPResponse


class MPBase:
    """Abstract base for MercadoPago API resources.

    Subclasses inherit ``_get``, ``_post``, ``_put``, and ``_delete``
    helpers that transparently manage authentication headers, JSON
    serialisation, timeout, and retry logic.  Each resource class only
    needs to define its own public methods mapping to specific API
    endpoints.

    Attributes:
        request_options: The :class:`RequestOptions` instance used by
            default when callers do not supply a per-call override.
        config: Read-only :class:`Config` with SDK constants.
    """

    def __init__(self, request_options, http_client):
        """Initialises the base with shared request options and HTTP client.

        Args:
            request_options: Default options (token, timeout, retries, headers)
                applied to every call unless overridden.
            http_client: :class:`~mercadopago.http.http_client.HttpClient`
                instance used to execute HTTP requests.

        Raises:
            ValueError: If *request_options* is not a
                :class:`RequestOptions` instance.
        """
        if not isinstance(request_options, RequestOptions):
            raise ValueError(
                "Param request_options must be a RequestOptions Object")

        self.__request_options = request_options
        self.__http_client = http_client
        self.__config = Config()

    def __check_request_options(self, request_options):
        """Resolves and validates per-call request options.

        Falls back to the instance-level defaults when *request_options*
        is ``None``, and ensures the access token is always present.
        """
        if request_options is not None and not isinstance(request_options, RequestOptions):
            raise ValueError(
                "Param request_options must be a RequestOptions Object")
        if request_options is None:
            request_options = self.__request_options

        if request_options.access_token is None:
            request_options.access_token = self.__request_options.access_token

        return request_options

    def __check_headers(self, request_options, extra_headers=None):
        """Builds the merged header dict for a request.

        Starts from the headers generated by *request_options* (or the
        instance default) and layers on any *extra_headers* such as
        Content-Type.
        """
        headers = self.__request_options.get_headers()
        if request_options is not None:
            headers = request_options.get_headers()

        if extra_headers is not None:
            headers.update(extra_headers)

        return headers

    def _path_param(self, value):
        """Encodes a dynamic URL path segment."""
        return quote(str(value), safe="")

    def _get(self, uri, filters=None, request_options=None):
        """Performs an authenticated GET request.

        Args:
            uri: API path relative to the base URL (e.g. ``/v1/payments``).
            filters: Optional query-string parameters.
            request_options: Per-call overrides; falls back to instance defaults.

        Raises:
            ValueError: If *filters* is provided but is not a ``dict``.

        Returns:
            dict: ``{"status": <http_code>, "response": <parsed_json>}``.
        """
        if filters is not None and not isinstance(filters, dict):
            raise ValueError("Filters must be a Dictionary")

        request_options = self.__check_request_options(request_options)
        headers = self.__check_headers(
            request_options, {"Content-type": self.__config.mime_json})

        return MPResponse(self.__http_client.get(
            url=self.__config.api_base_url + uri,
            params=filters,
            headers=headers,
            timeout=request_options.connection_timeout,
            maxretries=request_options.max_retries,
            retry_on=request_options.retry_on,
        ))

    def _post(self, uri, data=None, params=None, request_options=None):
        """Performs an authenticated POST request.

        Args:
            uri: API path relative to the base URL.
            data: Request body dict; JSON-encoded automatically.
            params: Optional query-string parameters.
            request_options: Per-call overrides; falls back to instance defaults.

        Returns:
            dict: ``{"status": <http_code>, "response": <parsed_json>}``.
        """
        if data is not None:
            data = JSONEncoder().encode(data)

        request_options = self.__check_request_options(request_options)
        headers = self.__check_headers(
            request_options, {"Content-type": self.__config.mime_json})

        return MPResponse(self.__http_client.post(
            url=self.__config.api_base_url + uri,
            data=data,
            params=params,
            headers=headers,
            timeout=request_options.connection_timeout,
            maxretries=request_options.max_retries,
            retry_on=request_options.retry_on,
        ))

    def _put(self, uri, data=None, params=None, request_options=None):
        """Performs an authenticated PUT request."""
        if data is not None:
            data = JSONEncoder().encode(data)

        request_options = self.__check_request_options(request_options)
        headers = self.__check_headers(
            request_options, {"Content-type": self.__config.mime_json})

        return MPResponse(self.__http_client.put(
            url=self.__config.api_base_url + uri,
            data=data,
            params=params,
            headers=headers,
            timeout=request_options.connection_timeout,
            maxretries=request_options.max_retries,
            retry_on=request_options.retry_on,
        ))

    def _delete(self, uri, params=None, request_options=None):
        """Performs an authenticated DELETE request.

        Args:
            uri: API path relative to the base URL.
            params: Optional query-string parameters.
            request_options: Per-call overrides; falls back to instance defaults.

        Returns:
            dict: ``{"status": <http_code>, "response": <parsed_json>}``.
        """
        request_options = self.__check_request_options(request_options)
        headers = self.__check_headers(request_options)

        return MPResponse(self.__http_client.delete(
            url=self.__config.api_base_url + uri,
            params=params,
            headers=headers,
            timeout=request_options.connection_timeout,
            maxretries=request_options.max_retries,
            retry_on=request_options.retry_on,
        ))

    @property
    def request_options(self):
        """Default :class:`RequestOptions` for this resource instance."""
        return self.__request_options

    @property
    def config(self):
        """Read-only :class:`Config` with SDK-wide constants."""
        return self.__config
