# webapp_management/invitations/views.py

from django.contrib.auth import get_user_model
from django.contrib.auth.tokens import default_token_generator
from django.utils.encoding import force_str
from django.utils.http import urlsafe_base64_decode

from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.http import Http404
from django.conf import settings


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

User = get_user_model()

# django_core_micha/invitations/viewsets.py


from .models import AccessCode
from .serializers import AccessCodeSerializer
from .conf import ACCESS_CODE_REGISTRATION_ENABLED
from .permissions import user_has_access_code_admin_rights

ACCESS_CODE_REGISTRATION_ENABLED = getattr(
    settings,
    "ACCESS_CODE_REGISTRATION_ENABLED",
    False,
)

class AccessCodeViewSet(viewsets.ModelViewSet):
    """
    Generic Access-Code API:

    - list/create/destroy/update: only for admins (configurable via ACCESS_CODE_ADMIN_ROLES)
    - POST /access-codes/validate/ : public, no CSRF, checks if code is valid
    - POST /access-codes/set/      : admin only, creates or updates a code
    """

    queryset = AccessCode.objects.all()
    serializer_class = AccessCodeSerializer

    def get_permissions(self):
        # validate → öffentlich
        if self.action == "validate":
            return [AllowAny()]

        # set + Standard-CRUD → nur Admins
        return [IsAuthenticated()]

    # Zentraler Admin-Check, damit du ihn nur einmal pflegen musst
    def _require_admin(self, request):
        if not user_has_access_code_admin_rights(request.user):
            return Response(
                {"detail": "Permission denied."},
                status=status.HTTP_403_FORBIDDEN,
            )
        return None

    # --------- Public validate ---------
    @action(
        detail=False,
        methods=["post"],
        url_path="validate",
        permission_classes=[AllowAny],
        authentication_classes=[],  # keine Session → kein CSRF
    )
    def validate(self, request):
        """
        Public endpoint for registration flow:
        POST {"code": "..."} → {"valid": true/false}
        """

        if not ACCESS_CODE_REGISTRATION_ENABLED:
            # Verhalten, wenn Feature deaktiviert:
            return Response(
                {"detail": "Access code registration is disabled."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        code = request.data.get("code", "").strip()
        if not code:
            return Response(
                {"detail": "Code not provided."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        valid = AccessCode.objects.filter(code=code, is_active=True).exists()
        return Response({"valid": valid}, status=status.HTTP_200_OK)

    # --------- Admin-only: set ---------
    @action(
        detail=False,
        methods=["post"],
        url_path="set",
    )
    def set(self, request):
        """
        Admin-only endpoint to create or update an access code.

        POST {"code": "ABC123", "is_active": true}

        - if code exists → update is_active
        - else           → create new code
        """
        # Admin-Berechtigung prüfen
        deny = self._require_admin(request)
        if deny is not None:
            return deny

        code = request.data.get("code", "").strip()
        is_active = bool(request.data.get("is_active", True))

        if not code:
            return Response(
                {"detail": "Code not provided."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        obj, created = AccessCode.objects.update_or_create(
            code=code,
            defaults={
                "is_active": is_active,
                "created_by": request.user,
            },
        )
        serializer = self.get_serializer(obj)
        return Response(
            serializer.data,
            status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
        )

    # Optional: du kannst auch list/create/destroy zusätzlich absichern:
    def list(self, request, *args, **kwargs):
        deny = self._require_admin(request)
        if deny is not None:
            return deny
        return super().list(request, *args, **kwargs)

    def create(self, request, *args, **kwargs):
        deny = self._require_admin(request)
        if deny is not None:
            return deny
        return super().create(request, *args, **kwargs)

    def destroy(self, request, *args, **kwargs):
        deny = self._require_admin(request)
        if deny is not None:
            return deny
        return super().destroy(request, *args, **kwargs)


class PasswordResetConfirmView(APIView):
    """
    Final step of the invite / password reset flow (unauthenticated):

    - GET  → checks if UID + token are valid
    - POST → sets a new password for the user

    Typical SPA flow:
      1. User clicks email link (e.g. /invite/<uid>/<token>/ or /reset/<uid>/<token>/)
      2. Frontend reads uid & token from the URL
      3. Frontend calls:
         - GET  /api/users/password-reset/<uid>/<token>/   → "valid?" check
         - POST /api/users/password-reset/<uid>/<token>/   → set new password
    """

    permission_classes = [AllowAny]

    link_valid_message = "Reset link is valid."
    link_invalid_message = "Reset link is invalid."
    missing_password_message = "New password not provided."
    invalid_link_message = "Reset link is invalid."
    success_message = "Password changed successfully."

    def get_user_from_uid(self, uidb64):
        try:
            uid = force_str(urlsafe_base64_decode(uidb64))
            return User.objects.get(pk=uid)
        except Exception:
            return None

    def get(self, request, uidb64, token, *args, **kwargs):
        user = self.get_user_from_uid(uidb64)
        if user and default_token_generator.check_token(user, token):
            return Response({"detail": self.link_valid_message})
        return Response(
            {"detail": self.link_invalid_message},
            status=status.HTTP_400_BAD_REQUEST,
        )

    def post(self, request, uidb64, token, *args, **kwargs):
        new_pw = request.data.get("new_password")
        if not new_pw:
            return Response(
                {"detail": self.missing_password_message},
                status=status.HTTP_400_BAD_REQUEST,
            )

        user = self.get_user_from_uid(uidb64)
        if not user:
            return Response(
                {"detail": self.invalid_link_message},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if not default_token_generator.check_token(user, token):
            return Response(
                {"detail": self.link_invalid_message},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Password policy
        try:
            validate_password(new_pw, user=user)
        except ValidationError as exc:
            return Response(
                {"detail": exc.messages},
                status=status.HTTP_400_BAD_REQUEST,
            )

        user.set_password(new_pw)
        user.save()

        return Response({"detail": self.success_message})