
# from .utils import merge_dicts
from async_openai.utils.configs import logger

class OpenAIError(Exception):
    def __init__(
        self,
        message=None,
        http_body=None,
        http_status=None,
        json_body=None,
        headers=None,
        code=None,
    ):
        super(OpenAIError, self).__init__(message)

        if http_body and hasattr(http_body, "decode"):
            try:
                http_body = http_body.decode("utf-8")
            except BaseException:
                http_body = (
                    "<Could not decode body as utf-8. "
                    "Please report to support@openai.com>"
                )

        self._message = message
        self.http_body = http_body
        self.http_status = http_status
        self.json_body = json_body
        self.headers = headers or {}
        self.code = code
        self.request_id = self.headers.get("request-id", None)
        self.error = self.construct_error_object()
        self.organization = self.headers.get("openai-organization", None)

    def __str__(self):
        msg = self._message or "<empty message>"
        if self.request_id is not None:
            return "Request {0}: {1}".format(self.request_id, msg)
        else:
            return msg

    # Returns the underlying `Exception` (base class) message, which is usually
    # the raw message returned by OpenAI's API. This was previously available
    # in python2 via `error.message`. Unlike `str(error)`, it omits "Request
    # req_..." from the beginning of the string.
    @property
    def user_message(self):
        return self._message

    def __repr__(self):
        return "%s(message=%r, http_status=%r, request_id=%r)" % (
            self.__class__.__name__,
            self._message,
            self.http_status,
            self.request_id,
        )

    def construct_error_object(self):
        if (
            self.json_body is None
            or "error" not in self.json_body
            or not isinstance(self.json_body["error"], dict)
        ):
            return None
        
        # values = merge_dicts({"message": None, "type": None}, values)


        # return openai.api_resources.error_object.ErrorObject.construct_from(
        #     self.json_body["error"]
        # )


class APIError(OpenAIError):
    pass


class TryAgain(OpenAIError):
    pass


class Timeout(OpenAIError):
    pass


class APIConnectionError(OpenAIError):
    def __init__(
        self,
        message,
        http_body=None,
        http_status=None,
        json_body=None,
        headers=None,
        code=None,
        should_retry=False,
    ):
        super(APIConnectionError, self).__init__(
            message, http_body, http_status, json_body, headers, code
        )
        self.should_retry = should_retry


class InvalidRequestError(OpenAIError):
    def __init__(
        self,
        message,
        param,
        code=None,
        http_body=None,
        http_status=None,
        json_body=None,
        headers=None,
    ):
        super(InvalidRequestError, self).__init__(
            message, http_body, http_status, json_body, headers, code
        )
        self.param = param

    def __repr__(self):
        return "%s(message=%r, param=%r, code=%r, http_status=%r, " "request_id=%r)" % (
            self.__class__.__name__,
            self._message,
            self.param,
            self.code,
            self.http_status,
            self.request_id,
        )

    def __reduce__(self):
        return type(self), (
            self._message,
            self.param,
            self.code,
            self.http_body,
            self.http_status,
            self.json_body,
            self.headers,
        )


class AuthenticationError(OpenAIError):
    pass


class PermissionError(OpenAIError):
    pass


class RateLimitError(OpenAIError):
    pass


class ServiceUnavailableError(OpenAIError):
    pass


class InvalidAPIType(OpenAIError):
    pass


class SignatureVerificationError(OpenAIError):
    def __init__(self, message, sig_header, http_body=None):
        super(SignatureVerificationError, self).__init__(message, http_body)
        self.sig_header = sig_header

    def __reduce__(self):
        return type(self), (
            self._message,
            self.sig_header,
            self.http_body,
        )


def handle_error_response(
    rbody, 
    rcode, 
    resp, 
    rheaders, 
    stream_error=False
):
    try:
        error_data = resp["error"]
    except (KeyError, TypeError) as e:
        raise APIError("Invalid response object from API: %r (HTTP response code " "was %d)" % (rbody, rcode), rbody, rcode, resp,) from e

    if "internal_message" in error_data:
        error_data["message"] += "\n\n" + error_data["internal_message"]

    logger(
        "OpenAI API error received",
        error_code=error_data.get("code"),
        error_type=error_data.get("type"),
        error_message=error_data.get("message"),
        error_param=error_data.get("param"),
        stream_error=stream_error,
        level = "error"
    )

    # Rate limits were previously coded as 400's with code 'rate_limit'
    if rcode == 429:
        return RateLimitError(
            error_data.get("message"), rbody, rcode, resp, rheaders
        )
    elif rcode in [400, 404, 415]:
        return InvalidRequestError(
            error_data.get("message"),
            error_data.get("param"),
            error_data.get("code"),
            rbody,
            rcode,
            resp,
            rheaders,
        )
    elif rcode == 401:
        return AuthenticationError(
            error_data.get("message"), rbody, rcode, resp, rheaders
        )
    elif rcode == 403:
        return PermissionError(
            error_data.get("message"), rbody, rcode, resp, rheaders
        )
    elif rcode == 409:
        return TryAgain(
            error_data.get("message"), rbody, rcode, resp, rheaders
        )
    elif stream_error:
        # TODO: we will soon attach status codes to stream errors
        parts = [error_data.get("message"), "(Error occurred while streaming.)"]
        message = " ".join([p for p in parts if p is not None])
        return APIError(message, rbody, rcode, resp, rheaders)
    else:
        return APIError(
            f"{error_data.get('message')} {rbody} {rcode} {resp} {rheaders}",
            rbody,
            rcode,
            resp,
            rheaders,
        )


# def interpret_response_line(
#     rbody, 
#     rcode, 
#     rheaders, 
#     stream: bool
# ) -> OpenAIResponse:
#     # HTTP 204 response code does not have any content in the body.
#     if rcode == 204:
#         return OpenAIResponse(None, rheaders)

#     if rcode == 503:
#         raise ServiceUnavailableError(
#             "The server is overloaded or not ready yet.",
#             rbody,
#             rcode,
#             headers=rheaders,
#         )
#     try:
#         if hasattr(rbody, "decode"):
#             rbody = rbody.decode("utf-8")
#         data = json.loads(rbody)
#     except (json.JSONDecodeError, UnicodeDecodeError):
#         raise APIError(
#             f"HTTP code {rcode} from API ({rbody})", rbody, rcode, headers=rheaders
#         )
#     resp = OpenAIResponse(data, rheaders)
#     # In the future, we might add a "status" parameter to errors
#     # to better handle the "error while streaming" case.
#     stream_error = stream and "error" in resp.data
#     if stream_error or not 200 <= rcode < 300:
#         raise handle_error_response(
#             rbody, rcode, resp.data, rheaders, stream_error=stream_error
#         )
#     return resp