# Copyright 2023 The KServe Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# coding: utf-8

# This implementation has been borrowed from kserve/kserve repository
# https://github.com/kserve/kserve/blob/release-0.11/python/kserve/kserve/exceptions.py
from __future__ import annotations

import six
from hopsworks_apigen import also_available_as


@also_available_as("hsml.client.istio.grpc.exceptions.OpenApiException")
class OpenApiException(Exception):
    """The base exception class for all OpenAPIExceptions."""


@also_available_as("hsml.client.istio.grpc.exceptions.ApiTypeError")
class ApiTypeError(OpenApiException, TypeError):
    def __init__(
        self,
        msg: str,
        path_to_item: list | None = None,
        valid_classes: tuple | None = None,
        key_type: bool | None = None,
    ) -> None:
        """Raises an exception for TypeErrors.

        Parameters:
            msg: the exception message
            path_to_item: a list of keys an indices to get to the current_item; None if unset
            valid_classes: the primitive classes that current item should be an instance of; None if unset
            key_type: False if our value is a value in a dict; True if it is a key in a dict; False if our item is an item in a list; None if unset
        """
        self.path_to_item = path_to_item
        self.valid_classes = valid_classes
        self.key_type = key_type
        full_msg = msg
        if path_to_item:
            full_msg = f"{msg} at {render_path(path_to_item)}"
        super().__init__(full_msg)


@also_available_as("hsml.client.istio.grpc.exceptions.ApiValueError")
class ApiValueError(OpenApiException, ValueError):
    def __init__(self, msg: str, path_to_item: list | None = None):
        """An error related to API value.

        Parameters:
            msg: the exception message.
            path_to_item: the path to the exception in the received_data dict.
        """
        self.path_to_item = path_to_item
        full_msg = msg
        if path_to_item:
            full_msg = f"{msg} at {render_path(path_to_item)}"
        super().__init__(full_msg)


@also_available_as("hsml.client.istio.grpc.exceptions.ApiKeyError")
class ApiKeyError(OpenApiException, KeyError):
    def __init__(self, msg: str, path_to_item: list | None = None):
        """An error related to the API key.

        Parameters:
            msg: the exception message.
            path_to_item:
                the path to the exception in the received_data dict
        """
        self.path_to_item = path_to_item
        full_msg = msg
        if path_to_item:
            full_msg = f"{msg} at {render_path(path_to_item)}"
        super().__init__(full_msg)


@also_available_as("hsml.client.istio.grpc.exceptions.ApiException")
class ApiException(OpenApiException):
    def __init__(self, status=None, reason=None, http_resp=None):
        if http_resp:
            self.status = http_resp.status
            self.reason = http_resp.reason
            self.body = http_resp.data
            self.headers = http_resp.getheaders()
        else:
            self.status = status
            self.reason = reason
            self.body = None
            self.headers = None

    def __str__(self):
        """Custom error messages for exception."""
        error_message = f"({self.status})\nReason: {self.reason}\n"
        if self.headers:
            error_message += f"HTTP response headers: {self.headers}\n"

        if self.body:
            error_message += f"HTTP response body: {self.body}\n"

        return error_message


@also_available_as("hsml.client.istio.grpc.exceptions.render_path")
def render_path(path_to_item: list) -> str:
    """Returns a string representation of a path.

    Parameters:
        path_to_item: A list of path parts.

    Returns:
        A string representation of the path.
    """
    result = ""
    for pth in path_to_item:
        if isinstance(pth, six.integer_types):
            result += f"[{pth}]"
        else:
            result += f"['{pth}']"
    return result
