from logging import getLogger
from re import findall
from time import perf_counter

from starlette.types import ASGIApp, Message, Receive, Scope, Send

from core.config import settings

LOCALHOST = '127.0.0.1'
BROWSERS = {
    'firefox': 'Firefox',
    'yabrowser': 'Yandex',
    'samsungbrowser': 'Samsung Internet',
    'trident': 'Internet Explorer',
    'opera': 'Opera',
    'vivaldi': 'Vivaldi',
    'brave': 'Brave',
    'edg': 'Edge',
    'chrome': 'Chrome',
    'safari': 'Safari',
    'chromium': 'Chromium',
    'msie': 'Internet Explorer',
}


class AccessLogMiddleware:
    def __init__(self, app: ASGIApp):
        self.app = app
        self.logger = getLogger(__name__)

        self.version = (
            b'Version',
            f'{settings.VERSION}'.encode(),
        )

    async def detect_browser(self, headers: dict[bytes, bytes]):
        if b'user-agent' not in headers:
            return 'unknown'

        user_agent = headers[b'user-agent'].decode().lower()

        for k, v in BROWSERS.items():
            if findall(k, user_agent):
                return v

        return 'unknown'

    @staticmethod
    async def get_client_ip(
        headers: dict[bytes, bytes],
        default_ip: str = LOCALHOST,
    ):
        if b'x-forwarded-for' not in headers:
            return default_ip

        ips = headers[b'x-forwarded-for'].decode().split(',')

        if len(ips) > 1:
            return ips[-1].strip()

        return ips[0]

    async def __call__(self, scope: Scope, receive: Receive, send: Send):
        if scope['type'] != 'http':
            return await self.app(scope, receive, send)

        start_time = perf_counter()

        async def send_wrapper(message: Message) -> None:
            if message['type'] != 'http.response.start':
                return await send(message)

            headers = dict(scope.get('headers', []))

            client_ip = await self.get_client_ip(headers, scope['client'][0])
            browser = await self.detect_browser(headers)

            response_time = (perf_counter() - start_time) * 1000
            response_data = f'dur={response_time:.2f}'
            response = (
                b'Server-Timing',
                f'resp;{response_data};desc="Response Time"'.encode(),
            )

            message['headers'] = message['headers'] + [response, self.version]

            self.logger.info(
                '%s - %s %s %d [%0.2fms]',
                client_ip,
                scope['method'],
                scope['path'],
                message['status'],
                response_time,
                extra={
                    'tags': {
                        'method': scope['method'],
                        'path': scope['path'],
                        'status': message['status'],
                        'response_time': response_time,
                        'client_ip': client_ip,
                        'browser': browser,
                    },
                },
            )

            await send(message)

        return await self.app(scope, receive, send_wrapper)
