from typing import Any

import msgspec
from starlette.applications import Starlette
from starlette.authentication import AuthCredentials, AuthenticationBackend, BaseUser, SimpleUser, requires
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.requests import HTTPConnection, Request
from starlette.responses import JSONResponse
from starlette.routing import Route

from csirtmu.perun_sync.config import AppConfig, AuthConfig
from csirtmu.perun_sync.repository import PerunUserRepository


class MSGSpecJSONResponse(JSONResponse):
    def render(self, content: Any) -> bytes:
        return msgspec.json.encode(content)


@requires("authenticated")
async def get_user_data(request: Request) -> MSGSpecJSONResponse:
    return MSGSpecJSONResponse(
        [user.to_struct() for user in await request.app.state.repository.get_user_by_mail(request.query_params["mail"])]
    )


class TokenAuthBackend(AuthenticationBackend):
    def __init__(self, config: AuthConfig) -> None:
        self.config = config

    async def authenticate(self, conn: HTTPConnection) -> tuple[AuthCredentials, BaseUser] | None:
        if "Authorization" not in conn.headers:
            return None
        auth_headers = conn.headers["Authorization"].split()
        if len(auth_headers) != 2 or auth_headers[0].lower() != "token":
            return None
        if len(credentials := [token for token in self.config.tokens if token.token == auth_headers[1]]) != 1:
            return None
        return AuthCredentials(["authenticated"]), SimpleUser(credentials[0].name)


def app(*args: list[Any], **kwargs: dict[str, Any]) -> Starlette:  # noqa: ARG001
    c = AppConfig.get()

    routes = [
        Route("/user", get_user_data, methods=["GET"]),
    ]
    middlewares = [
        Middleware(AuthenticationMiddleware, backend=TokenAuthBackend(c.auth)),
    ]

    app_ = Starlette(
        routes=routes,
        middleware=middlewares,
    )
    app_.state.config = c
    app_.state.repository = PerunUserRepository(c.database)
    return app_
