from typing import Any, Dict, List, Optional

from fastapi import Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

from learninghouse.core.logging import logger
from learninghouse.models import (
    LearningHouseErrorMessage,
    LearningHouseValidationErrorMessage,
)

MIMETYPE_JSON = "application/json"


class LearningHouseException(Exception):
    STATUS_CODE = status.HTTP_500_INTERNAL_SERVER_ERROR
    UNKNOWN = "UNKNOWN"
    DESCRIPTION = "An unknown exception occurred " + "while handling your request."

    def __init__(
        self,
        status_code: Optional[int] = None,
        key: Optional[str] = None,
        description: Optional[str] = None,
        headers: Optional[Dict[str, str]] = None,
    ):
        super().__init__()
        self.http_status_code: int = status_code or self.STATUS_CODE
        self.error: LearningHouseErrorMessage = LearningHouseErrorMessage(
            error=key or self.UNKNOWN, description=description or self.DESCRIPTION
        )
        self.headers = headers if headers else {}

    def response(self) -> JSONResponse:
        return JSONResponse(
            content=self.error.dict(),
            status_code=self.http_status_code,
            headers=self.headers,
        )

    @classmethod
    def api_description(cls) -> Dict:
        return {
            "model": LearningHouseErrorMessage,
            "description": "An exception occured which is not handled by the service now. "
            + "Please write an issue on GitHub.",
            "content": {
                MIMETYPE_JSON: {
                    "example": {"error": cls.UNKNOWN, "description": cls.DESCRIPTION}
                }
            },
        }


class LearningHouseSecurityException(LearningHouseException):
    STATUS_CODE = status.HTTP_403_FORBIDDEN
    SECURITY_EXCEPTION = "SECURITY_EXCEPTION"
    DESCRIPTION = "A security violation occured while handling your request."

    def __init__(self, description: str, key: Optional[str] = None):
        key = key or self.SECURITY_EXCEPTION

        super().__init__(self.STATUS_CODE, key, description or self.DESCRIPTION)

    @classmethod
    def api_description(cls) -> Dict:
        return {
            "model": LearningHouseErrorMessage,
            "description": "The request didn't pass security checks.",
            "content": {
                MIMETYPE_JSON: {
                    "example": {
                        "error": cls.SECURITY_EXCEPTION,
                        "description": cls.DESCRIPTION,
                    }
                }
            },
        }


class LearningHouseUnauthorizedException(LearningHouseException):
    STATUS_CODE = status.HTTP_401_UNAUTHORIZED
    UNAUTHORIZED = "UNAUTHORIZED"
    DESCRIPTION = "Could not validate credentials"

    def __init__(self, description: Optional[str] = None):
        super().__init__(
            self.STATUS_CODE,
            self.UNAUTHORIZED,
            description or self.DESCRIPTION,
            {"WWW-Authenticate": "Bearer"},
        )

    @classmethod
    def api_description(cls) -> Dict:
        return {
            "model": LearningHouseErrorMessage,
            "description": "The request didn't pass security checks.",
            "content": {
                MIMETYPE_JSON: {
                    "example": {
                        "error": cls.UNAUTHORIZED,
                        "description": cls.DESCRIPTION,
                    }
                }
            },
        }


class LearningHouseValidationError(LearningHouseException):
    STATUS_CODE = status.HTTP_422_UNPROCESSABLE_ENTITY
    VALIDATION_ERROR = "VALIDATION_ERROR"
    DESCRIPTION = "A validation error occurred while handling your request."

    def __init__(self, error: Optional[RequestValidationError] = None):
        super().__init__(
            self.STATUS_CODE, self.VALIDATION_ERROR, str(error) or self.DESCRIPTION
        )
        self.validations: List[Dict[str, Any]] = error.errors()

    @classmethod
    def api_description(cls) -> Dict:
        return {
            "model": LearningHouseValidationErrorMessage,
            "description": "The request didn't pass input validation",
            "content": {
                MIMETYPE_JSON: {
                    "example": {
                        "error": cls.VALIDATION_ERROR,
                        "description": cls.DESCRIPTION,
                        "validations": [
                            {
                                "loc": ["body", "..."],
                                "msg": "example",
                                "type": "value...",
                                "ctx": {},
                            }
                        ],
                    }
                }
            },
        }

    def response(self) -> JSONResponse:
        validation_error = LearningHouseValidationErrorMessage.from_error_message(
            self.error, self.validations
        )
        return JSONResponse(
            content=validation_error.dict(), status_code=self.http_status_code
        )


async def validation_error_handler(
    _: Request, exc: RequestValidationError
) -> JSONResponse:
    return LearningHouseValidationError(exc).response()


async def learninghouse_exception_handler(_: Request, exc: LearningHouseException):
    response = exc.response()

    if isinstance(
        exc, (LearningHouseSecurityException, LearningHouseUnauthorizedException)
    ):
        logger.warning(exc.error.description)

    return response
