"""
esi_requests.models
~~~~~~~~~~~~~~~~~~~~

This module defines the data models used in esi_requests.

Models:
    - PreparedESIRequest: A prepared request object, used to send a request.
    - ESIResponse: The response object returned from an API call.

These models should not be created directly by users, but rather are returned by the `Session` object's request methods.

Some docstrings and code generated by ChatGPT, a large language model trained by OpenAI.

(c) 2023 by Hanbo Guo
"""

import json
from dataclasses import dataclass
from typing import Dict, Optional

from .exceptions import ESIResponseError


class ESIRequest:
    """Holds user-provided parameters for a request to ESI.

    An ESIRequest instance is created with parameters like the request endpoint,
    HTTP method, query parameters, and request headers. This instance is then passed to
    the `prepare_request()` method in the `Session` class to create a `PreparedESIRequest`
    instance which can be sent as an HTTP request to the ESI server via `send()` method.

    Attributes:
        endpoint (str): An endpoint of EVE ESI.
        method (str): The HTTP method for the request.
        params (dict): The query parameters to include in the request.
        headers (dict): The headers for the request.
        prepared (bool): Whether the request is prepared or not.
    """

    def __init__(self, endpoint: str, method: str, params: Dict, headers: Dict, **kwargs) -> None:
        self.endpoint = endpoint
        self.method = method
        self.params = params
        self.headers = headers
        self.kwargs = kwargs

        self.prepared = False


class PreparedESIRequest:
    """
    The prepared request object with necessary details to be sent to ESI server. 

    Note:
        User should not create ``PreparedESIRequest`` but gets it from API calls via ``ESISession.prepare_request()`` method.
    """
    def __init__(self, endpoint: str = None, method: str = None, url: str = None, params: Dict = None,
                 headers: Dict = None, kwargs: Dict = None):
        """
        Args:
            endpoint (str): The endpoint for the request.
            method (str): The HTTP method for the request (e.g. GET, POST).
            url (str): The full URL for the request, including the endpoint and any query parameters.
            params (dict): A dictionary containing any query parameters to include in the request.
            headers (dict): A dictionary containing any headers to include in the request.
            kwargs (dict): Any additional arguments to be passed to the request function.
        """
        self.endpoint: str = endpoint
        self.method: str = method
        self.url: str = url
        self.params: Dict = params or {}
        self.headers: Dict = headers or {}
        self.kwargs: Dict = kwargs or {}

    def __repr__(self) -> str:
        return f"<PreparedESIRequest [{self.method}]>"


@dataclass
class ESIResponse:
    """The response object returned by ESI API calls.

    Attributes:
        status (int): The HTTP status code of the response.
        reason (Optional[str]): The reason-phrase from aiohttp.ClientResponse.
        headers (dict): A case-insensitive dictionary of HTTP response headers.
        url (str): The URL of the request.
        request_info (ESIRequest): A reference to the `PreparedESIRequest` object.
        text (Optional[str]): The response body, decoded using utf-8.
        expires (Optional[str]): A RFC7231 formatted datetime string, if any.

    Note:
        Do not create an `ESIResponse` object directly, but retrieve it from ESI API calls.

        ESI API uses `utf-8` encoding for response bodies.

    Methods:
        __bool__(): Returns `True` if `status` is `200` or `304`, `False` otherwise.
        __repr__(): Returns a string representation of the `ESIResponse` object.
        ok: Returns `True` if `status` is `200` or `304`, `False` otherwise.
        raise_for_status(): Raises an `ESIResponseError` if `status` is greater than or equal to 400.
        json(): Returns the JSON-deserialized response body.
    """


    status: int = None  # e.g. 404, 200
    reason: Optional[str] = None
    headers: Dict = None
    url: str = None
    request_info: "ESIRequest" = None
    text: Optional[str] = None
    expires: Optional[str] = None

    def __bool__(self) -> bool:
        return self.ok

    def __repr__(self) -> str:
        return f"<Response [{self.status}]>"

    @property
    def ok(self) -> bool:
        """Returns ``True`` if ``status`` is ``200`` or ``304``, ``False`` if not.

        This is **not** a check for ``200 OK``.
        """
        return self.status in (200, 304)

    def raise_for_status(self):
        if self.status >= 400:
            raise ESIResponseError(self.status, self.request_info, self.reason)

    def json(self):
        """Returns json deserialized response body.

        Note that ESI all uses ``utf-8`` for encoding.
        """
        return json.loads(self.text)
