"""
Tenant-aware mixins for views and serializers
"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.shortcuts import redirect
from rest_framework import serializers


class TenantAccessMixin:
    """
    Mixin that validates hospital access for views.

    This mixin ensures:
    - Users can only access objects from their hospital
    - PX admins can access all hospitals
    - Hospital admins can only access their hospital
    - Department managers can only access their department
    - Source Users can only access their own created data
    """

    def get_object(self, queryset=None):
        """Retrieve object with tenant validation."""
        obj = super().get_object(queryset)
        user = self.request.user

        # Source Users can only access their own created data
        if user.is_source_user():
            if hasattr(obj, 'created_by'):
                if obj.created_by != user:
                    raise PermissionDenied("You can only access data you created")
            return obj

        # Check if user has access to this object's hospital
        if hasattr(obj, 'hospital'):
            if not self.can_access_hospital(obj.hospital):
                raise PermissionDenied("You don't have access to this hospital's data")

        return obj

    def get_queryset(self):
        """Filter queryset based on user's hospital and role."""
        queryset = super().get_queryset()
        user = self.request.user

        # Source Users can only see their own created data
        if user.is_source_user():
            if hasattr(queryset.model, 'created_by'):
                return queryset.filter(created_by=user)
            return queryset.none()

        # PX Admins can see all hospitals
        if user.is_px_admin():
            return queryset

        # Users without a hospital cannot see any records
        if not user.hospital:
            return queryset.none()

        # Filter by user's hospital
        queryset = queryset.filter(hospital=user.hospital)

        # Department managers can only see their department's records
        if user.is_department_manager() and user.department:
            if hasattr(queryset.model, 'department'):
                queryset = queryset.filter(department=user.department)

        return queryset

    def can_access_hospital(self, hospital):
        """Check if user can access given hospital."""
        user = self.request.user

        # PX Admins can access all hospitals
        if user.is_px_admin():
            return True

        # Users can only access their own hospital
        if user.hospital == hospital:
            return True

        return False


class TenantSerializerMixin:
    """
    Mixin that validates hospital field in serializers.

    This mixin ensures:
    - Users can only create records for their hospital
    - PX admins can create records for any hospital
    - Hospital field is validated and set automatically
    """

    def validate_hospital(self, value):
        """Ensure user can create records for this hospital."""
        user = self.context['request'].user

        # PX admins can assign to any hospital
        if user.is_px_admin():
            return value

        # Users must create records for their own hospital
        if user.hospital != value:
            raise serializers.ValidationError(
                "You can only create records for your hospital"
            )

        return value

    def to_internal_value(self, data):
        """Set hospital from user's profile if not provided."""
        # Convert data to mutable dict if needed
        mutable_data = data.copy() if hasattr(data, 'copy') else data

        user = self.context['request'].user

        # Auto-set hospital if not provided and user has one
        if 'hospital' not in mutable_data or not mutable_data['hospital']:
            if user.hospital:
                mutable_data['hospital'] = str(user.hospital.id)

        return super().to_internal_value(mutable_data)


class TenantAdminMixin:
    """
    Mixin for Django admin with tenant isolation.

    This mixin ensures:
    - Admin users only see their hospital's records
    - PX admins see all records
    - New records are automatically assigned to user's hospital
    """

    def get_queryset(self, request):
        """Filter queryset based on user's hospital."""
        qs = super().get_queryset(request)

        # PX Admins can see all hospitals
        if request.user.is_px_admin():
            return qs

        # Users with a hospital can only see their hospital's records
        if request.user.hospital:
            qs = qs.filter(hospital=request.user.hospital)

        return qs

    def save_model(self, request, obj, form, change):
        """Auto-assign hospital on create."""
        if not change and hasattr(obj, 'hospital') and not obj.hospital:
            obj.hospital = request.user.hospital
        super().save_model(request, obj, form, change)

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        """Limit foreign key choices to user's hospital."""
        if db_field.name == 'hospital':
            # Only PX admins can select any hospital
            if not request.user.is_px_admin():
                # Filter to user's hospital
                kwargs['queryset'] = db_field.related_model.objects.filter(
                    id=request.user.hospital.id
                )

        # Filter department choices to user's hospital
        if db_field.name == 'department':
            kwargs['queryset'] = db_field.related_model.objects.filter(
                hospital=request.user.hospital
            )

        return super().formfield_for_foreignkey(db_field, request, **kwargs)


class TenantRequiredMixin(LoginRequiredMixin):
    """
    Mixin that ensures user has hospital context.

    This mixin ensures:
    - User is authenticated (from LoginRequiredMixin)
    - User has a hospital assigned (or is PX Admin)
    - Redirects PX Admins to hospital selector if no hospital selected
    - Redirects other users to error page if no hospital assigned
    """

    def dispatch(self, request, *args, **kwargs):
        """Check hospital context before processing request."""
        response = super().dispatch(request, *args, **kwargs)

        # PX Admins need to select a hospital
        if request.user.is_px_admin():
            if not request.tenant_hospital:
                return redirect('core:select_hospital')

        # Other users must have a hospital assigned
        elif not request.user.hospital:
            return redirect('core:no_hospital_assigned')

        return response
