# 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.

from keystoneauth1.exceptions import base as kae_base
from keystoneauth1.exceptions import http as kae_http
from openstack import exceptions as sdkexc
from oslo_serialization import jsonutils
from requests import exceptions as reqexc

from senlinclient.common.i18n import _

verbose = False


class BaseException(Exception):
    """An error occurred."""
    def __init__(self, message=None):
        self.message = message

    def __str__(self):
        return self.message or self.__class__.__doc__


class CommandError(BaseException):
    """Invalid usage of CLI."""


class FileFormatError(BaseException):
    """Illegal file format detected."""


class PollingExceededError(BaseException):
    """Desired resource state not achived within polling period."""


class HTTPException(BaseException):
    """Base exception for all HTTP-derived exceptions."""
    code = 'N/A'

    def __init__(self, error=None):
        super(HTTPException, self).__init__(error)
        try:
            self.error = error
            if 'error' not in self.error:
                raise KeyError(_('Key "error" not exists'))
        except KeyError:
            # If key 'error' does not exist, self.message becomes
            # no sense. In this case, we return doc of current
            # exception class instead.
            self.error = {'error': {'message': self.__class__.__doc__}}
        except Exception:
            self.error = {'error':
                          {'message': self.message or self.__class__.__doc__}}

    def __str__(self):
        message = self.error['error'].get('message', 'Internal Error')
        if verbose:
            traceback = self.error['error'].get('traceback', '')
            return (_('ERROR: %(message)s\n%(traceback)s') %
                    {'message': message, 'traceback': traceback})
        else:
            code = self.error['error'].get('code', 'Unknown')
            return _('ERROR(%(code)s): %(message)s') % {'code': code,
                                                        'message': message}


class ClientError(HTTPException):
    pass


class ServerError(HTTPException):
    pass


class HTTPBadRequest(ClientError):
    # 400
    pass


class HTTPUnauthorized(ClientError):
    # 401
    pass


class HTTPForbidden(ClientError):
    # 403
    pass


class HTTPNotFound(ClientError):
    # 404
    pass


class HTTPMethodNotAllowed(ClientError):
    # 405
    pass


class HTTPNotAcceptable(ClientError):
    # 406
    pass


class HTTPProxyAuthenticationRequired(ClientError):
    # 407
    pass


class HTTPRequestTimeout(ClientError):
    # 408
    pass


class HTTPConflict(ClientError):
    # 409
    pass


class HTTPGone(ClientError):
    # 410
    pass


class HTTPLengthRequired(ClientError):
    # 411
    pass


class HTTPPreconditionFailed(ClientError):
    # 412
    pass


class HTTPRequestEntityTooLarge(ClientError):
    # 413
    pass


class HTTPRequestURITooLong(ClientError):
    # 414
    pass


class HTTPUnsupportedMediaType(ClientError):
    # 415
    pass


class HTTPRequestRangeNotSatisfiable(ClientError):
    # 416
    pass


class HTTPExpectationFailed(ClientError):
    # 417
    pass


class HTTPInternalServerError(ServerError):
    # 500
    pass


class HTTPNotImplemented(ServerError):
    # 501
    pass


class HTTPBadGateway(ServerError):
    # 502
    pass


class HTTPServiceUnavailable(ServerError):
    # 503
    pass


class HTTPGatewayTimeout(ServerError):
    # 504
    pass


class HTTPVersionNotSupported(ServerError):
    # 505
    pass


class ConnectionRefused(HTTPException):
    # 111
    pass


_EXCEPTION_MAP = {
    111: ConnectionRefused,
    400: HTTPBadRequest,
    401: HTTPUnauthorized,
    403: HTTPForbidden,
    404: HTTPNotFound,
    405: HTTPMethodNotAllowed,
    406: HTTPNotAcceptable,
    407: HTTPProxyAuthenticationRequired,
    408: HTTPRequestTimeout,
    409: HTTPConflict,
    410: HTTPGone,
    411: HTTPLengthRequired,
    412: HTTPPreconditionFailed,
    413: HTTPRequestEntityTooLarge,
    414: HTTPRequestURITooLong,
    415: HTTPUnsupportedMediaType,
    416: HTTPRequestRangeNotSatisfiable,
    417: HTTPExpectationFailed,
    500: HTTPInternalServerError,
    501: HTTPNotImplemented,
    502: HTTPBadGateway,
    503: HTTPServiceUnavailable,
    504: HTTPGatewayTimeout,
    505: HTTPVersionNotSupported,
}


def parse_exception(exc):
    """Parse exception code and yield useful information.

    :param exc: details of the exception.
    """
    if isinstance(exc, sdkexc.HttpException):
        if exc.details is None:
            data = exc.response.json()
            code = data.get('code', None)
            message = data.get('message', None)
            error = data.get('error', None)
            if error:
                record = {
                    'error': {
                        'code': exc.http_status,
                        'message': message or exc.message
                    }
                }
            else:
                info = data.values()[0]
                record = {
                    'error': {
                        'code': info.get('code', code),
                        'message': info.get('message', message)
                    }
                }
        else:
            try:
                record = jsonutils.loads(exc.details)
            except Exception:
                # If the exc.details is not in JSON format
                record = {
                    'error': {
                        'code': exc.http_status,
                        'message': exc,
                    }
                }
    elif isinstance(exc, reqexc.RequestException):
        # Exceptions that are not captured by SDK
        record = {
            'error': {
                'code': exc.message[1].errno,
                'message': exc.message[0],
            }
        }

    elif isinstance(exc, str):
        record = jsonutils.loads(exc)
    # some exception from keystoneauth1 is not shaped by SDK
    elif isinstance(exc, kae_http.HttpError):
        record = {
            'error': {
                'code': exc.http_status,
                'message': exc.message
            }
        }
    elif isinstance(exc, kae_base.ClientException):
        record = {
            'error': {
                # other exceptions from keystoneauth1 is an internal
                # error to senlin, so set status code to 500
                'code': 500,
                'message': exc.message
            }
        }
    else:
        print(_('Unknown exception: %s') % exc)
        return

    try:
        code = record['error']['code']
    except KeyError as err:
        print(_('Malformed exception record, missing field "%s"') % err)
        print(_('Original error record: %s') % record)
        return

    if code in _EXCEPTION_MAP:
        inst = _EXCEPTION_MAP.get(code)
        raise inst(record)
    else:
        raise HTTPException(record)
