import time
from uuid import uuid4

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response

from api.route import Route
from core.logger import APILogger

logger = APILogger("api.api")


class LoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        await self._log_request(request)
        response = await call_next(request)
        await self._log_after_request(request, response)

        return response

    @staticmethod
    def _filter_relevant_headers(headers: dict) -> dict:
        relevant_keys = {
            "content-type",
            "host",
            "connection",
            "user-agent",
            "origin",
            "referer",
            "accept",
        }
        return {key: value for key, value in headers.items() if key in relevant_keys}

    @classmethod
    async def _log_request(cls, request: Request):
        request.state.request_id = uuid4()
        request.state.start_time = time.time()
        logger.debug(
            f"Request {request.state.request_id}: {request.method}@{request.url.path} from {request.client.host}"
        )
        user = await Route.get_user()

        request_info = {
            "headers": cls._filter_relevant_headers(dict(request.headers)),
            "args": dict(request.query_params),
            "form-data": (
                await request.form()
                if request.headers.get("content-type")
                == "application/x-www-form-urlencoded"
                else None
            ),
            "payload": (
                await request.json()
                if request.headers.get("content-length") == "0"
                else None
            ),
            "user": f"{user.id}-{user.keycloak_id}" if user else None,
            "files": (
                {key: file.filename for key, file in (await request.form()).items()}
                if await request.form()
                else None
            ),
        }

        logger.trace(f"Request {request.state.request_id}: {request_info}")

    @staticmethod
    async def _log_after_request(request: Request, response: Response):
        duration = (time.time() - request.state.start_time) * 1000
        logger.info(
            f"Request finished {request.state.request_id}: {response.status_code}-{request.method}@{request.url.path} from {request.client.host} in {duration:.2f}ms"
        )
