import time
from typing import Dict, Any, Callable
from fastapi import Request, Response
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse

from ...core.exception import exception_to_dict, RequestException

from ..routes.response import return_response
from ...log import footprint


__all__ = (
    'Runtime',
)


class Runtime:

    def __init__(self):
        pass

    @staticmethod
    async def get_request_body(request: Request) -> Dict[str, Any]:
        content_length = request.headers.get('content-length')
        content_type = request.headers.get('content-type')
        try:
            if content_length and int(content_length) > (1 * 1024 * 1024):
                return {}
            body = await request.body()
            return {
                'content_length': content_length,
                'content_type': content_type,
                'json': jsonable_encoder(body.decode('utf-8')),
            }
        except Exception:
            return {
                'content_length': content_length,
                'content_type': content_type,
            }

    @staticmethod
    async def create_payload(request: Request, exception: Exception) -> Dict[str, Any]:
        body = await Runtime.get_request_body(request)
        return jsonable_encoder({
            'path': request.url.path,
            'method': request.method,
            'query_parameters': request.query_params,
            'path_parameters': request.path_params,
            'headers': request.headers,
            'body': body,
            **exception_to_dict(exception),
        })

    async def __call__(self, request: Request, call_next: Callable) -> Response:
        start_time = time.time()
        try:
            response = await call_next(request)
        except RequestException as e:
            payload = await self.create_payload(request, e)
            if not e.skip_footprint:
                footprint.leave(
                    log_type='warning',
                    message=e.message,
                    controller=e.controller,
                    subject='Request Error',
                    payload=payload,
                )

            return return_response(
                data=str(e.message),
                status_code=e.status_code,
                response_class=JSONResponse,
            )
        except Exception as e:
            payload = await self.create_payload(request, e)
            try:
                message = payload.get('args', ['Unrecognized Error'])[0]
            except:
                message = 'Unrecognized Error has happened.'

            footprint.leave(
                log_type='error',
                message=message,
                controller='runtime',
                subject='Unrecognized Error',
                payload=payload,
            )

            return return_response(
                data='An unexpected issue has occurred; our team has been notified and is working diligently to resolve it promptly.',
                status_code=500,
                response_class=JSONResponse,
            )
        else:
            response.headers["X-Process-Time"] = str(round((time.time() - start_time) * 1000))
            return response
