# SPDX-FileCopyrightText: 2026 Martin Kirchgessner
#
# SPDX-License-Identifier: AGPL-3.0-or-later

"""
This module contains our extensions to the
[Quart-Auth](https://github.com/pgjones/quart-auth)
extension, that handles secure cookie authentication.
"""

from collections.abc import Awaitable, Callable
from datetime import datetime
from functools import wraps

from quart import current_app, g, redirect, request, url_for
from quart_auth import (
    AuthUser,
    Unauthorized,
    current_user,
    login_required,  # noqa: F401 so modules can import login_required from here
    logout_user,
)

from radiotomate.models import LoggedOutUser, Session, User


async def redirect_to_login(*_):
    """
    This is meant to be registered as an error handler of `Unauthorized` exceptions,
    and transforms them into a redirection to the login form.
    """
    return redirect(url_for("login.log_in"))


class RadiotomateAuth(AuthUser):
    """
    This preloads two attributes to Quart's `current_user` object:
    * `user`: the user object
    * `session`: the session object
    it also updates the session object, to track users' activity.

    It is invoked in a hook created by `radiotomate.interface_app.app_factory`.
    """

    def __init__(self, session_id):
        super().__init__(session_id)
        self.user: User = None
        self.session: Session = None

    async def preload_attributes(self, update_latest_action=True):
        if self.auth_id:
            self.session = await Session.from_id(g.dbsession, self.auth_id)
            if self.session:
                if not self.session.active:
                    logout_user()
                    raise Unauthorized("This session has been closed")
                self.user = self.session.user
                if self.session.latest_address != request.remote_addr:
                    self.session.latest_address = request.remote_addr
                if update_latest_action:
                    self.session.latest_action = datetime.now()
                await g.dbsession.commit()
            else:
                logout_user()
                raise Unauthorized("Invalid session")
        else:
            self.session = None
            self.user = LoggedOutUser()


def permission_required[**P](permission: str) -> Callable[P, Awaitable[object]]:
    """
    A decorator for Quart methods that checks current users' roles in Radiotomate.
    For example:

        @blueprint.delete("/carts/<int:cart_id>/sounds/<int:sound_id>")
        @permission_required("carts")
        async def delete_sound(cart_id: int, sound_id: int):
            ...

    This is a more specialized variant of QuartAuth's `@login_required`.
    """

    def decorator(
        func: Callable[P, Awaitable[object]],
    ) -> Callable[P, Awaitable[object]]:
        @wraps(func)
        async def wrapper(*args: P.args, **kwargs: P.kwargs):
            if not await current_user.is_authenticated:
                raise Unauthorized()
            else:
                checker = getattr(current_user.user, "can_" + permission)
                if not checker():
                    raise Unauthorized()
                else:
                    return await current_app.ensure_async(func)(*args, **kwargs)

        return wrapper

    return decorator


def token_required[**P, T](
    func: Callable[P, Awaitable[T]],
) -> Callable[P, Awaitable[T]]:
    """A decorator that restricts route access between radiotomate's processes.

    This should be used to wrap a view function to ensure the request contains
    the correct token in the `X-Auth-Token` header.
    The value is configured in `playout_process_config.token` in the config file.

    Otherwise, raises `quart.exceptions.Unauthorized`.
    """

    @wraps(func)
    async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
        if (
            not request.headers.get("X-Auth-Token")
            == current_app.config["PLAYOUT_TOKEN"]
        ):
            raise Unauthorized()
        else:
            return await current_app.ensure_async(func)(*args, **kwargs)

    return wrapper
