import linecache
import re
from collections.abc import Mapping
from typing import NamedTuple

from httpx import AsyncClient
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route

from starlette_proxy_router.exceptions import AttributeAuthUserError
from starlette_proxy_router.parser import EndpointArgs, MethodHTTP
from starlette_proxy_router.types import AuthType


class DataRequest(NamedTuple):
    headers: Mapping[str, str]
    query_params: Mapping[str, str]
    path_params: Mapping[str, str]
    body: bytes


async def _parse_request(request: Request, is_body: bool = True) -> DataRequest:
    headers = {key.decode(): value.decode() for key, value in request.scope["headers"]}
    return DataRequest(
        headers=headers,
        query_params=request.query_params,
        body=await request.body(),
        path_params=request.path_params,
    )


def _build_target_url(host: str, target: str, path_params: Mapping[str, str]) -> str:
    target = target.format_map(path_params)
    return f"{host}{target[1:]}"


_AUTH_CODE_LINES = """
    is_auth = {is_auth}
    auth_type = '{auth_type}'
    auth_fields = {auth_fields}

    query_params = {{key: value for key, value in request.query_params}}
    headers = request.headers

    if is_auth:
        if auth_type == AuthType.QUERY_PARAMS:
            for field in auth_fields:
                try:
                    query_params[field] = request.user.__dict__[field]
                except AttributeError as e:
                    raise AttributeAuthUserError(field)
        else:
            for field in auth_fields:
                try:
                    headers[field.replace('_', '-').capitalize()] = request.user.__dict__[field]
                except AttributeError as e:
                    raise AttributeAuthUserError(field)
"""
_HTTP_PROXY_ENDPOINT_IS_BODY = """
async def {func_name}(request):
    data_request = await _parse_request(request)
    target_url = _build_target_url('{host}', '{target}', data_request.path_params)

    {auth_code_lines}

    async with AsyncClient() as ac:
        res = await ac.{method}(
            target_url,
            params=query_params,
            json=data_request.body.decode(),
            timeout={timeout}
        )
        return Response(
            content=res.content,
            status_code=res.status_code,
            headers=res.headers,
        )
"""
_HTTP_PROXY_ENDPOINT_NOT_BODY = """
async def {func_name}(request):
    data_request = await _parse_request(request, is_body=False)
    target_url = _build_target_url('{host}', '{target}', data_request.path_params)

    {auth_code_lines}

    async with AsyncClient() as ac:
        res = await ac.{method}(
            target_url,
            params=query_params,
            timeout={timeout}
        )
        return Response(
            content=res.content,
            status_code=res.status_code,
            headers=res.headers,
        )
"""
_METHOD_BODIES = {
    MethodHTTP.GET: _HTTP_PROXY_ENDPOINT_NOT_BODY,
    MethodHTTP.POST: _HTTP_PROXY_ENDPOINT_IS_BODY,
    MethodHTTP.PUT: _HTTP_PROXY_ENDPOINT_IS_BODY,
    MethodHTTP.DELETE: _HTTP_PROXY_ENDPOINT_NOT_BODY,
}


class RouteCompilier:
    __slots__ = ()

    def compile_route(self, endpoint_args: EndpointArgs) -> Route:
        func_name = self._build_func_name(endpoint_args.path)
        body_http_endpoint = _METHOD_BODIES[endpoint_args.method]
        body_http_endpoint = body_http_endpoint.format_map({
            "auth_code_lines": _AUTH_CODE_LINES,
            "func_name": func_name,
            "method": endpoint_args.method.value,
            "target": endpoint_args.target,
            "host": endpoint_args.host,
            "timeout": endpoint_args.timeout,
        }).format_map({
            "is_auth": endpoint_args.is_auth,
            "auth_type": endpoint_args.auth_type,
            "auth_fields": endpoint_args.auth_fields,
        })
        global_vars = {
            "AsyncClient": AsyncClient,
            "Response": Response,
            "AuthType": AuthType,
            "AttributeAuthUserError": AttributeAuthUserError,
            "_parse_request": _parse_request,
            "_build_target_url": _build_target_url,
        }
        file_name = f"{endpoint_args.service_name}_route"
        lines = body_http_endpoint.splitlines(keepends=True)
        linecache.cache[file_name] = (
            len(body_http_endpoint),
            None,
            lines,
            file_name,
        )
        compiled = compile(body_http_endpoint, file_name, "exec")
        exec(compiled, global_vars)

        return Route(
            endpoint_args.path,
            global_vars[func_name],  # type: ignore
            methods=[endpoint_args.method],
        )

    def _build_func_name(self, path: str) -> str:
        sub_paths = re.sub(r"/{[^{}]+}", "", path)
        sub_paths_list = re.split(r"[-_/]", sub_paths)
        result = "_".join(sub_paths_list[1:])

        if not result:
            return "homepage"
        return result
