# Copyright (c) 2021 CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com

from enum import Enum
from http import HTTPStatus

from aws_lambda_powertools import Logger
from voluptuous import Invalid

from czc import json
from czc.api.v2.constants import (CURSOR_PARAMETER, LIMIT_PARAMETER,
                                  OFFSET_PARAMETER, SORT_KEY_PARAMETER,
                                  SORT_ORDER_PARAMETER)

logger = Logger()


class APIErrorType(str, Enum):
    API = 'api_error'
    INVALID_REQUEST = 'invalid_request_error'

    @staticmethod
    def from_status_code(status_code: HTTPStatus):
        if status_code >= HTTPStatus.BAD_REQUEST and status_code < HTTPStatus.INTERNAL_SERVER_ERROR:
            return APIErrorType.INVALID_REQUEST
        return APIErrorType.API


class APIErrorCode(str, Enum):
    INVALID_FILTER = 'filter_parameter_invalid'
    INVALID_PAGINATION = 'pagination_parameter_invalid'
    INVALID_SORTING = 'sorting_parameter_invalid'
    NONE = ''


class ServiceError(Exception):
    """API Gateway and ALB HTTP Service Error"""

    def __init__(self, message: str, status_code: HTTPStatus = HTTPStatus.INTERNAL_SERVER_ERROR,
                 type: APIErrorType = None,
                 code: str = APIErrorCode.NONE):
        """
        Parameters
        ----------
        message: str
            Error message
        status_code: int, optional
            Http status code, by default HTTPStatus.INTERNAL_SERVER_ERROR
        type: APIErrorType, optional
            API Error Type, by default try to find this from the status_code
        code: APIErrorCode, optional
            API Error Code, by default APIErrorCode.NONE
        """
        self.message = message
        self.type: APIErrorType = type or APIErrorType.from_status_code(status_code)
        self.status_code = status_code
        self.code = code

    @property
    def body(self):
        return json.dumps({
            'error': {
                **({'code': self.code} if self.code else {}),
                'message': self.message,
                'type': self.type,
            }
        }, iterable_as_array=True)


class BadRequestError(ServiceError):
    """Bad Request (400)"""

    def __init__(self, message: str, code: str = '', invalid_exception: Invalid = None):
        if invalid_exception and not code:
            exception_string = str(invalid_exception)
            logger.debug(f'Invalid Exception string {exception_string}')
            code = APIErrorCode.NONE
            if any(f"'{x}'" in exception_string for x in {LIMIT_PARAMETER, OFFSET_PARAMETER, CURSOR_PARAMETER}):
                code = APIErrorCode.INVALID_PAGINATION
            if any(f"'{x}'" in exception_string for x in {SORT_KEY_PARAMETER, SORT_ORDER_PARAMETER}):
                code = APIErrorCode.INVALID_SORTING
        super().__init__(message, status_code=HTTPStatus.BAD_REQUEST, type=APIErrorType.INVALID_REQUEST, code=code)


class UnauthorizedError(ServiceError):
    """Unauthorized Error (401)"""

    def __init__(self, message: str):
        super().__init__(message, status_code=HTTPStatus.UNAUTHORIZED, type=APIErrorType.INVALID_REQUEST)


class ForbiddenError(ServiceError):
    """Forbidden Error (403)"""

    def __init__(self, message: str):
        super().__init__(message, status_code=HTTPStatus.FORBIDDEN, type=APIErrorType.INVALID_REQUEST)


class NotFoundError(ServiceError):
    """Not Found Error (404)"""

    def __init__(self, message: str = "Not found"):
        super().__init__(message, status_code=HTTPStatus.NOT_FOUND)


class InternalServerError(ServiceError):
    """Internal Server Error (500)"""

    def __init__(self, message: str):
        super().__init__(message, status_code=HTTPStatus.INTERNAL_SERVER_ERROR, type=APIErrorType.API)
