"""
Authentication views for the API.
Login, logout, register, and current user endpoints.
"""

from rest_framework import status
from rest_framework.throttling import ScopedRateThrottle
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.decorators import api_view, permission_classes
from django.contrib.auth import authenticate, login, logout
from django.views.decorators.csrf import ensure_csrf_cookie
from django.utils.decorators import method_decorator
from drf_spectacular.utils import extend_schema

from .serializers import UserSerializer


class LoginView(APIView):
    """Login with email and password, returns session cookie."""
    permission_classes = [AllowAny]
    throttle_classes = [ScopedRateThrottle]
    throttle_scope = "login"

    @extend_schema(
        request={
            'application/json': {
                'type': 'object',
                'properties': {
                    'email': {'type': 'string', 'format': 'email'},
                    'password': {'type': 'string'},
                },
                'required': ['email', 'password'],
            }
        },
        responses={200: UserSerializer}
    )
    def post(self, request):
        email = request.data.get('email')
        password = request.data.get('password')

        if not email or not password:
            return Response(
                {'error': 'Email and password are required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        user = authenticate(request, username=email, password=password)

        if user is None:
            return Response(
                {'error': 'Invalid credentials'},
                status=status.HTTP_401_UNAUTHORIZED
            )

        login(request, user)
        return Response(UserSerializer(user, context={"request": request}).data)


class LogoutView(APIView):
    """Logout and clear session."""
    permission_classes = [AllowAny]

    @extend_schema(responses={200: {'type': 'object', 'properties': {'status': {'type': 'string'}}}})
    def post(self, request):
        logout(request)
        return Response({'status': 'logged out'})


class RegisterView(APIView):
    """Register a new user."""
    permission_classes = [AllowAny]
    throttle_classes = [ScopedRateThrottle]
    throttle_scope = "login"

    @extend_schema(
        request={
            'application/json': {
                'type': 'object',
                'properties': {
                    'email': {'type': 'string', 'format': 'email'},
                    'password': {'type': 'string'},
                    'password_confirm': {'type': 'string'},
                },
                'required': ['email', 'password', 'password_confirm'],
            }
        },
        responses={201: UserSerializer}
    )
    def post(self, request):
        from django.contrib.auth import get_user_model
        User = get_user_model()

        email = request.data.get('email')
        password = request.data.get('password')
        password_confirm = request.data.get('password_confirm')

        if not email or not password or not password_confirm:
            return Response(
                {'error': 'Email, password, and password confirmation are required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        if password != password_confirm:
            return Response(
                {'error': 'Passwords do not match'},
                status=status.HTTP_400_BAD_REQUEST
            )

        if User.objects.filter(email=email).exists():
            return Response(
                {'error': 'A user with this email already exists'},
                status=status.HTTP_400_BAD_REQUEST
            )

        user = User.objects.create_user(
            email=email,
            password=password,
            first_name=request.data.get('first_name', ''),
            last_name=request.data.get('last_name', ''),
        )
        login(request, user)
        return Response(
            UserSerializer(user, context={"request": request}).data,
            status=status.HTTP_201_CREATED,
        )


class MeView(APIView):
    """Current authenticated user. GET returns profile, PATCH updates it (incl. avatar upload)."""
    permission_classes = [IsAuthenticated]

    def get_parsers(self):
        from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
        return [JSONParser(), MultiPartParser(), FormParser()]

    @extend_schema(responses={200: UserSerializer})
    def get(self, request):
        return Response(UserSerializer(request.user, context={"request": request}).data)

    @extend_schema(request=UserSerializer, responses={200: UserSerializer})
    def patch(self, request):
        serializer = UserSerializer(
            request.user, data=request.data, partial=True, context={"request": request}
        )
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)


# Function-based shim retained for URL backwards compat
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def me(request):
    """Get current authenticated user."""
    return Response(UserSerializer(request.user, context={"request": request}).data)


@api_view(['GET'])
@permission_classes([AllowAny])
def session_check(request):
    """Check if user has a valid session."""
    if request.user.is_authenticated:
        return Response({
            'authenticated': True,
            'user': UserSerializer(request.user, context={"request": request}).data
        })
    return Response({'authenticated': False})


class ChangePasswordView(APIView):
    """Authenticated user changes their own password. Clears must_change_password."""
    permission_classes = [IsAuthenticated]

    @extend_schema(
        request={
            'application/json': {
                'type': 'object',
                'properties': {
                    'current_password': {'type': 'string'},
                    'new_password': {'type': 'string'},
                },
                'required': ['current_password', 'new_password'],
            }
        },
        responses={200: UserSerializer},
    )
    def post(self, request):
        current = request.data.get('current_password')
        new = request.data.get('new_password')
        if not current or not new:
            return Response({'error': 'current_password and new_password required'},
                            status=status.HTTP_400_BAD_REQUEST)
        user = request.user
        if not user.check_password(current):
            return Response({'error': 'Current password incorrect'},
                            status=status.HTTP_400_BAD_REQUEST)
        if len(new) < 8:
            return Response({'error': 'New password must be at least 8 characters'},
                            status=status.HTTP_400_BAD_REQUEST)
        if new == current:
            return Response({'error': 'New password must differ from current'},
                            status=status.HTTP_400_BAD_REQUEST)
        user.set_password(new)
        user.must_change_password = False
        user.save(update_fields=['password', 'must_change_password'])
        # Re-auth session after password change
        from django.contrib.auth import update_session_auth_hash
        update_session_auth_hash(request, user)
        return Response(UserSerializer(user, context={"request": request}).data)


@method_decorator(ensure_csrf_cookie, name='dispatch')
class CSRFView(APIView):
    """Issue CSRF cookie + return token in body.

    Cookie covers same-site frontends; body token enables cross-origin
    frontends (different eTLD+1) where JS cannot read the cookie.
    """
    permission_classes = [AllowAny]

    @extend_schema(responses={200: None})
    def get(self, request):
        from django.middleware.csrf import get_token
        return Response({'csrfToken': get_token(request)})


class PasswordResetRequestView(APIView):
    """Request a password reset link by email."""
    permission_classes = [AllowAny]
    throttle_classes = [ScopedRateThrottle]
    throttle_scope = "login"

    @extend_schema(
        request={
            'application/json': {
                'type': 'object',
                'properties': {'email': {'type': 'string', 'format': 'email'}},
                'required': ['email'],
            }
        },
        responses={200: None},
    )
    def post(self, request):
        from django.contrib.auth import get_user_model
        from django.contrib.auth.tokens import default_token_generator
        from django.utils.http import urlsafe_base64_encode
        from django.utils.encoding import force_bytes
        from django.conf import settings as dj_settings
        from apps.notifications.email import send_to_user

        email = request.data.get('email')
        if not email:
            return Response({'error': 'Email is required'}, status=status.HTTP_400_BAD_REQUEST)

        User = get_user_model()
        user = User.objects.filter(email=email).first()
        if user:
            uid = urlsafe_base64_encode(force_bytes(user.pk))
            token = default_token_generator.make_token(user)
            link = f"/reset-password?uid={uid}&token={token}"
            # send_to_user rather than _send: it writes an EmailLog row on both
            # success and failure. Without it a user reporting "I never got the
            # reset link" leaves no record of whether one was ever sent, and the
            # enumeration-safe 200 below hides the failure from the caller too.
            send_to_user(
                user,
                title=f"Reset your {dj_settings.PRODUCT_NAME} password",
                body=(
                    "Use the link below to set a new password. If you didn't "
                    "request this, ignore this email."
                ),
                link=link,
                notification_type="password_reset",
            )
        # Always return 200 regardless of whether the email matched, so the
        # endpoint can't be used to enumerate registered accounts.
        return Response({'detail': 'If that email exists, a reset link has been sent.'})


class PasswordResetConfirmView(APIView):
    """Confirm a password reset using the uid/token from the emailed link."""
    permission_classes = [AllowAny]
    throttle_classes = [ScopedRateThrottle]
    throttle_scope = "login"

    @extend_schema(
        request={
            'application/json': {
                'type': 'object',
                'properties': {
                    'uid': {'type': 'string'},
                    'token': {'type': 'string'},
                    'new_password': {'type': 'string'},
                },
                'required': ['uid', 'token', 'new_password'],
            }
        },
        responses={200: None},
    )
    def post(self, request):
        from django.contrib.auth import get_user_model
        from django.contrib.auth.tokens import default_token_generator
        from django.utils.http import urlsafe_base64_decode

        uid = request.data.get('uid')
        token = request.data.get('token')
        new_password = request.data.get('new_password')
        if not uid or not token or not new_password:
            return Response({'error': 'uid, token, and new_password are required'},
                             status=status.HTTP_400_BAD_REQUEST)
        if len(new_password) < 8:
            return Response({'error': 'New password must be at least 8 characters'},
                             status=status.HTTP_400_BAD_REQUEST)

        User = get_user_model()
        try:
            user = User.objects.get(pk=urlsafe_base64_decode(uid).decode())
        except (User.DoesNotExist, ValueError, TypeError, OverflowError):
            return Response({'error': 'Invalid or expired reset link'}, status=status.HTTP_400_BAD_REQUEST)

        if not default_token_generator.check_token(user, token):
            return Response({'error': 'Invalid or expired reset link'}, status=status.HTTP_400_BAD_REQUEST)

        user.set_password(new_password)
        user.save(update_fields=['password'])
        return Response({'detail': 'Password reset successfully.'})


class InvitationAcceptView(APIView):
    """Accept an org invitation by token. Public — the token is the auth.

    A new email creates the User (password required, then logs them in). An
    existing email just attaches the membership/role — no login-state check,
    since the token itself already proves ownership of the invited address.
    """
    permission_classes = [AllowAny]
    throttle_classes = [ScopedRateThrottle]
    throttle_scope = "login"

    @extend_schema(
        request={
            'application/json': {
                'type': 'object',
                'properties': {
                    'token': {'type': 'string'},
                    'password': {'type': 'string'},
                    'first_name': {'type': 'string'},
                    'last_name': {'type': 'string'},
                },
                'required': ['token'],
            }
        },
        responses={200: UserSerializer},
    )
    def post(self, request):
        from django.contrib.auth import get_user_model
        from django.utils import timezone
        from apps.companies.models import OrganizationMembership
        from apps.hr.signals import ensure_employee_stub
        from .models import Invitation, UserRole

        token = request.data.get('token')
        if not token:
            return Response({'error': 'token is required'}, status=status.HTTP_400_BAD_REQUEST)

        invite = Invitation.objects.filter(
            token=token, status='pending', expires_at__gt=timezone.now(),
        ).select_related('organization', 'role', 'donor_company').first()
        if invite is None:
            return Response({'error': 'Invalid or expired invitation'}, status=status.HTTP_400_BAD_REQUEST)

        User = get_user_model()
        user = User.objects.filter(email__iexact=invite.email).first()
        newly_created = user is None
        if newly_created:
            new_password = request.data.get('password') or ''
            if len(new_password) < 8:
                return Response({'error': 'A password of at least 8 characters is required'},
                                 status=status.HTTP_400_BAD_REQUEST)
            extra = (
                {'organization_role': 'donor', 'donor_company': invite.donor_company,
                 'donor_role': invite.donor_role or 'admin'}
                if invite.donor_company_id else {}
            )
            user = User.objects.create_user(
                email=invite.email,
                password=new_password,
                first_name=(request.data.get('first_name') or '').strip(),
                last_name=(request.data.get('last_name') or '').strip(),
                **extra,
            )

        OrganizationMembership.objects.get_or_create(organization=invite.organization, user=user)
        if invite.role_id:
            UserRole.objects.get_or_create(
                user=user, role=invite.role, organization=invite.organization,
                defaults={'assigned_by': invite.invited_by},
            )
        ensure_employee_stub(user)

        invite.status = 'accepted'
        invite.accepted_at = timezone.now()
        invite.save(update_fields=['status', 'accepted_at'])

        if newly_created:
            # Must name a backend listed in AUTHENTICATION_BACKENDS: Django
            # stores the path in the session and re-imports it on every later
            # request, so a stale path silently anonymizes the new session.
            login(request, user, backend='apps.core.auth_backends.AccountStatusModelBackend')

        return Response(UserSerializer(user, context={'request': request}).data)
