from __future__ import annotations

from contextlib import contextmanager
from typing import TYPE_CHECKING, Any

from django.contrib.auth.middleware import get_user
from django.http import HttpRequest
from django.utils.functional import SimpleLazyObject, empty

from allauth import app_settings as allauth_settings
from allauth.account.internal.stagekit import get_pending_stage
from allauth.core.exceptions import ImmediateHttpResponse
from allauth.headless import app_settings
from allauth.headless.constants import Client
from allauth.headless.internal import sessionkit


if TYPE_CHECKING:
    from allauth.account.stages import LoginStage


class AuthenticationStatus:
    def __init__(self, request: HttpRequest) -> None:
        self.request = request

    @property
    def is_authenticated(self) -> bool:
        return self.request.user.is_authenticated

    def get_pending_stage(self) -> LoginStage | None:
        return get_pending_stage(self.request)

    @property
    def has_pending_signup(self) -> bool:
        if not allauth_settings.SOCIALACCOUNT_ENABLED:
            return False
        from allauth.socialaccount.internal import flows

        return bool(flows.signup.get_pending_signup(self.request))


def purge_request_user_cache(request: HttpRequest) -> None:
    for attr in ["_cached_user", "_acached_user"]:
        if hasattr(request, attr):
            delattr(request, attr)
    if isinstance(request.user, SimpleLazyObject):
        request.user._wrapped = empty
    else:
        request.user = SimpleLazyObject(lambda: get_user(request))  # type: ignore[assignment]


@contextmanager
def authentication_context(request: HttpRequest):
    from allauth.headless.base.response import UnauthorizedResponse

    old_user = request.user
    old_session = request.session
    try:
        request.session = sessionkit.new_session()
        purge_request_user_cache(request)

        strategy = app_settings.TOKEN_STRATEGY
        session_token = strategy.get_session_token(request)
        if session_token:
            session = strategy.lookup_session(session_token)
            if not session:
                raise ImmediateHttpResponse(UnauthorizedResponse(request, status=410))
            request.session = session
            purge_request_user_cache(request)
        request.allauth.headless._pre_user = request.user  # type: ignore[attr-defined]
        # request.user is lazy -- force evaluation
        request.allauth.headless._pre_user.pk  # type: ignore[attr-defined]
        yield
    finally:
        if request.session.modified and not request.session.is_empty():
            request.session.save()
        request.user = old_user
        request.session = old_session
        # e.g. logging in calls csrf `rotate_token()` -- this prevents setting a new CSRF cookie.
        request.META["CSRF_COOKIE_NEEDS_UPDATE"] = False


def expose_access_token(request: HttpRequest) -> dict[str, Any] | None:
    """
    Determines if a new access token needs to be exposed.
    """
    if request.allauth.headless.client != Client.APP:  # type: ignore[attr-defined]
        return None
    if not request.user.is_authenticated:
        return None
    pre_user = request.allauth.headless._pre_user  # type: ignore[attr-defined]
    if pre_user.is_authenticated and pre_user.pk == request.user.pk:
        return None
    strategy = app_settings.TOKEN_STRATEGY
    return strategy.create_access_token_payload(request)
