# django_core_micha/auth/views.py
from django.http import JsonResponse
from django.views.decorators.csrf import ensure_csrf_cookie
from django.shortcuts import get_object_or_404, redirect
from django.contrib.auth import get_user_model
from django.utils import timezone
from django.urls import reverse
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth import login as auth_login

from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework import permissions, viewsets, status
from rest_framework.decorators import action

from django_core_micha.invitations.mixins import InviteActionsMixin
from django_core_micha.auth.roles import RolePolicy
from django_core_micha.auth.security import set_security_level

from allauth.mfa.models import Authenticator

from .recovery import RecoveryRequest
from .serializers import RecoveryRequestSerializer
from .permissions import IsSupportAgent, IsAssignedSupportOrAdmin
from .security import create_recovery_request_for_user
from .emails import send_recovery_email

from rest_framework.views import APIView
from django.utils.translation import gettext_lazy as _

from django_core_micha.auth.roles import get_role_level_for_user, ROLE_LEVEL_3
from django.conf import settings
from django.core.mail import send_mail


User = get_user_model()






def recovery_complete_view(request, token: str):
    try:
        rr = RecoveryRequest.objects.select_related("user").get(token=token)
    except RecoveryRequest.DoesNotExist:
        return redirect(f"{settings.PUBLIC_ORIGIN}/login?recovery=invalid")

    if not rr.is_active():
        return redirect(f"{settings.PUBLIC_ORIGIN}/login?recovery=expired")

    email = rr.user.email or ""
    target = f"{settings.PUBLIC_ORIGIN}/login?recovery={rr.token}"
    if email:
        target += f"&email={email}"

    return redirect(target)

def apply_recovery_login_if_applicable(request, user) -> bool:
    """
    Uses a recovery context from the session to turn a successful
    password login into a MFA-bypassed 'recovery' session.

    Returns True if recovery was applied (MFA should be skipped),
    False otherwise.
    """
    rr_id = request.session.get("recovery_request_id")
    rr_user_id = request.session.get("recovery_user_id")

    if not rr_id or not rr_user_id:
        return False

    # Recovery context only valid for this user
    if rr_user_id != user.id:
        return False

    try:
        rr = RecoveryRequest.objects.get(id=rr_id, user=user)
    except ObjectDoesNotExist:
        # Clean up broken context
        request.session.pop("recovery_request_id", None)
        request.session.pop("recovery_user_id", None)
        return False

    if not rr.is_active():
        # Clean up and ignore
        request.session.pop("recovery_request_id", None)
        request.session.pop("recovery_user_id", None)
        return False

    # At this point we have:
    # - correct password
    # - a valid recovery request for this user

    # Mark as completed
    rr.mark_completed()

    # Clean up context so link is effectively one-time
    request.session.pop("recovery_request_id", None)
    request.session.pop("recovery_user_id", None)

    # Elevate session to 'recovery' level
    set_security_level(request, "recovery")

    return True


class BaseUserViewSet(InviteActionsMixin, viewsets.ModelViewSet):
    """
    Core User API:
    - list/retrieve/update/delete: authenticated users
    - current: get/patch self
    - update-role: role management via RolePolicy
    """

    queryset = User.objects.all()
    serializer_class = None  # must be set in project
    permission_classes = [IsAuthenticated]
    role_policy_class = RolePolicy

    def get_role_policy(self):
        return self.role_policy_class()

    @action(
        detail=False,
        methods=["get", "patch"],
        permission_classes=[IsAuthenticated],
        url_path="current",
    )
    def current(self, request):
        if request.method == "GET":
            serializer = self.get_serializer(request.user)
            return Response(serializer.data)

        serializer = self.get_serializer(
            request.user, data=request.data, partial=True
        )
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

    @action(
        detail=True,
        methods=["patch"],
        permission_classes=[permissions.IsAuthenticated],
        url_path="update-role",
    )
    def update_role(self, request, pk=None):
        user = self.get_object()
        new_role = request.data.get("role")

        policy = self.get_role_policy()

        if not policy.is_valid_code(new_role):
            return Response(
                {"detail": "Invalid role."},
                status=400,
            )

        if not policy.can_change_role(request.user, user, new_role):
            return Response({"detail": "Permission denied."}, status=403)

        user.profile.role = new_role
        user.profile.save()
        return Response({"detail": "Role updated successfully."})
    
    @action(
        detail=False,
        methods=["post"],
        permission_classes=[AllowAny],
        url_path="mfa/support-help",
    )
    def mfa_support_help(self, request):
        """
        Called when the user clicks "I can't use any of these methods"
        on the MFA step. We do NOT require authentication here.
        The client sends an identifier (usually the e-mail).
        """
        identifier = request.data.get("email") or request.data.get("identifier")
        message = request.data.get("message", "")

        if not identifier:
            # 400: Client-Fehler, eigener Code
            return Response(
                {"code": "Auth.MFA_IDENTIFIER_REQUIRED"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        UserModel = get_user_model()
        try:
            user = UserModel.objects.get(email__iexact=identifier)
        except UserModel.DoesNotExist:
            # Kein Leak: gleicher Code/Status wie Erfolgsfall
            return Response(
                {"code": "Auth.MFA_HELP_REQUESTED"},
                status=status.HTTP_200_OK,
            )

        RecoveryRequest.objects.create(user=user, message=message)
        

        return Response(
            {"code": "Auth.MFA_HELP_REQUESTED"},
            status=status.HTTP_200_OK,
        )



@ensure_csrf_cookie
def csrf_token_view(request):
    return JsonResponse({"detail": "CSRF cookie set"})


class PasskeyViewSet(viewsets.ViewSet):
    permission_classes = [IsAuthenticated]

    def list(self, request):
        qs = Authenticator.objects.filter(
            user=request.user,
            type=Authenticator.Type.WEBAUTHN,
        )

        data = []
        for a in qs:
            wrapped = a.wrap()  # liefert WebAuthn-spezifische Properties
            data.append(
                {
                    "id": a.pk,
                    # Name wird aus .data rausgezogen
                    "name": getattr(wrapped, "name", None),
                    "created_at": a.created_at,
                    "last_used_at": a.last_used_at,
                    # optional, je nach allauth-Version vorhanden
                    "is_device_passkey": getattr(
                        wrapped, "is_device_passkey", None
                    ),
                }
            )

        return Response(data)

    def destroy(self, request, pk=None):
        obj = get_object_or_404(
            Authenticator,
            pk=pk,
            user=request.user,
            type=Authenticator.Type.WEBAUTHN,
        )
        obj.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

class RecoveryRequestViewSet(viewsets.ModelViewSet):
    """
    API for listing/handling recovery requests (support side) and
    creating a request from MFA-flow (user side).
    """

    queryset = RecoveryRequest.objects.all().select_related("user", "resolved_by")
    serializer_class = RecoveryRequestSerializer

    def get_permissions(self):
        # Neuer Public-Login-Action:
        if self.action == "recovery_login":
            return [AllowAny()]

        if self.action == "create_from_mfa":
            return [permissions.IsAuthenticated()]
        if self.action in ("list", "retrieve"):
            return [IsSupportAgent()]
        if self.action in ("approve", "reject"):
            return [IsAssignedSupportOrAdmin()]
        return super().get_permissions()

    def get_queryset(self):
        qs = super().get_queryset()
        status_param = self.request.query_params.get("status")
        if status_param:
            qs = qs.filter(status=status_param)
        return qs

    @action(methods=["post"], detail=True)
    def approve(self, request, pk=None):
        rr = self.get_object()

        if not IsAssignedSupportOrAdmin().has_object_permission(request, self, rr):
            return Response(
                {"detail": "Permission denied."},
                status=status.HTTP_403_FORBIDDEN,
            )

        support_note = request.data.get("support_note", "")

        # Build recovery URL
        path = reverse("mfa-recovery-complete", args=[rr.token])
        recovery_url = request.build_absolute_uri(path)

        # Mark as approved, store note
        rr.mark_resolved(
            RecoveryRequest.Status.APPROVED,
            by=request.user,
            note=support_note,
        )

        # **WICHTIG**: Mail an den User senden
        send_recovery_email(rr, recovery_url)

        serializer = self.get_serializer(rr)
        data = serializer.data
        data["recovery_link"] = recovery_url
        return Response(data)


    @action(methods=["post"], detail=True)
    def reject(self, request, pk=None):
        rr = self.get_object()

        if not IsAssignedSupportOrAdmin().has_object_permission(request, self, rr):
            return Response({"detail": "Permission denied."},
                            status=status.HTTP_403_FORBIDDEN)

        support_note = request.data.get("support_note", "")

        rr.mark_resolved(
            RecoveryRequest.Status.REJECTED,
            by=request.user,
            note=support_note,
        )
        serializer = self.get_serializer(rr)
        return Response(serializer.data)

    @action(
        methods=["post"],
        detail=False,
        permission_classes=[AllowAny],
        url_path=r"recovery-login/(?P<token>[^/.]+)",
    )
    def recovery_login(self, request, token=None):
        """
        Public Endpoint:
        - Token muss zu einem APPROVED RecoveryRequest gehören
        - User muss E-Mail + Passwort schicken
        - Dann klassischer Login, MFA wird ersetzt
        """
        identifier = request.data.get("email") or request.data.get("identifier")
        password = request.data.get("password")

        if not identifier or not password:
            return Response(
                {"code": "Auth.CREDENTIALS_REQUIRED"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            rr = RecoveryRequest.objects.select_related("user").get(
                token=token,
                status=RecoveryRequest.Status.APPROVED,
            )
        except RecoveryRequest.DoesNotExist:
            return Response(
                {"code": "Auth.RECOVERY_TOKEN_INVALID"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            user = User.objects.get(email__iexact=identifier)
        except User.DoesNotExist:
            return Response(
                {"code": "Auth.INVALID_CREDENTIALS"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if user.pk != rr.user_id:
            return Response(
                {"code": "Auth.INVALID_CREDENTIALS"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Passwort prüfen
        if not user.check_password(password):
            return Response(
                {"code": "Auth.INVALID_CREDENTIALS"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Klassisch einloggen
        auth_login(
            request,
            user,
            backend="django.contrib.auth.backends.ModelBackend",
        )

        # E-Mail + Passwort + Support-Freigabe => Level "basic"
        set_security_level(request, "basic")

        # RecoveryRequest abschliessen
        rr.mark_completed()

        return Response(
            {"status": 200, "code": "Auth.RECOVERY_LOGIN_OK"},
            status=status.HTTP_200_OK,
        )

