from os import PathLike
from typing import Union, List

from aiohttp.web_response import Response as AioResponse
from jinja2 import TemplateNotFound

from bolinette.utils import files, paths

from bolinette import blnt, exceptions


class Cookie:
    def __init__(self, name, value, *, path, http_only=True, expires=None, delete=False):
        self.name = name
        self.value = value
        self.expires = expires
        self.path = path
        self.http_only = http_only
        self.delete = delete


class APIResponse:
    def __init__(self, content, code):
        self.content = content
        self.code = code
        self.cookies = []


class Response:
    def __init__(self, context: 'blnt.BolinetteContext'):
        self._exceptions = {
            exceptions.UnauthorizedError: self.unauthorized,
            exceptions.BadRequestError: self.bad_request,
            exceptions.UnprocessableEntityError: self.unprocessable_entity,
            exceptions.ConflictError: self.conflict,
            exceptions.NotFoundError: self.not_found,
            exceptions.ForbiddenError: self.forbidden,
            exceptions.APIError: self.internal_server_error
        }
        self.context = context

    @staticmethod
    def _build_message(code, status, messages=None, data=None):
        if messages is None:
            messages = []
        if not isinstance(messages, list):
            messages = [messages]
        body = {
            'code': code,
            'status': status,
            'messages': messages
        }
        if data is not None:
            body['data'] = data
        return APIResponse(body, code)

    def ok(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(200, 'OK', messages, data)

    def created(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(201, 'CREATED', messages, data)

    def bad_request(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(400, 'BAD REQUEST', messages, data)

    def unauthorized(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(401, 'UNAUTHORIZED', messages, data)

    def forbidden(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(403, 'FORBIDDEN', messages, data)

    def not_found(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(404, 'NOT FOUND', messages, data)

    def method_not_allowed(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(405, 'METHOD NOT ALLOWED', messages, data)

    def conflict(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(409, 'CONFLICT', messages, data)

    def unprocessable_entity(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(422, 'UNPROCESSABLE ENTITY', messages, data)

    def internal_server_error(self, *, messages: Union[str, List[str]] = None, data=None):
        return self._build_message(500, 'INTERNAL SERVER ERROR', messages, data)

    def render_template(self, name: PathLike, params: dict = None, workdir: PathLike = None, *, status=200):
        if workdir is None:
            workdir = self.context.templates_path()
        if params is None:
            params = {}
        for key, value in self.context.manifest.items():
            if key not in params:
                params[key] = value
        try:
            content = files.render_template(workdir, name, params)
        except TemplateNotFound:
            error_404_wd = self.context.internal_files_path('templates')
            error_404 = paths.join('errors', '404.html.jinja2')
            content = files.render_template(error_404_wd, error_404, params)
            status = 404
        return AioResponse(body=content, status=status, content_type='text/html')

    def from_exception(self, exception: Union[exceptions.APIError, exceptions.APIErrors]):
        messages = []
        if isinstance(exception, exceptions.APIError):
            messages = [exception.message]
        elif isinstance(exception, exceptions.APIErrors):
            messages = [error.message for error in exception.errors]
            exception = exception.errors[0]
        for except_cls in self._exceptions:
            if isinstance(exception, except_cls):
                return self._exceptions[except_cls](messages=messages)
