"""
Schedule App - DRF ViewSets

API endpoints for Resources and Events with quota enforcement.
"""
from rest_framework import viewsets, status
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from django.core.exceptions import ValidationError as DjangoValidationError
from django.contrib.contenttypes.models import ContentType
from smoothschedule.communication.notifications.models import Notification
from .models import Resource, Event, Participant, ResourceType, ScheduledTask, TaskExecutionLog, PluginTemplate, PluginInstallation, EventPlugin, GlobalEventPlugin, Holiday, TimeBlock, Location
from .serializers import (
    ResourceSerializer, EventSerializer, ParticipantSerializer,
    CustomerSerializer, ServiceSerializer, ResourceTypeSerializer, StaffSerializer,
    ScheduledTaskSerializer, TaskExecutionLogSerializer, PluginInfoSerializer,
    PluginTemplateSerializer, PluginTemplateListSerializer, PluginInstallationSerializer,
    EventPluginSerializer, GlobalEventPluginSerializer,
    HolidaySerializer, HolidayListSerializer,
    TimeBlockSerializer, TimeBlockListSerializer, BlockedDateSerializer, CheckConflictsSerializer,
    LocationSerializer, StaffRoleSerializer,
)
from .services import LocationService
from .models import Service
from smoothschedule.identity.core.permissions import HasQuota
from smoothschedule.identity.core.mixins import (
    TenantFilteredQuerySetMixin,
    SandboxFilteredQuerySetMixin,
    UserTenantFilteredMixin,
    DenyStaffWritePermission,
    DenyStaffAllAccessPermission,
    DenyStaffListPermission,
    AutomationFeatureRequiredMixin,
    TaskFeatureRequiredMixin,
)
from smoothschedule.identity.users.models import User, StaffRole


class ResourceTypeViewSet(viewsets.ModelViewSet):
    """
    API endpoint for managing custom Resource Types.

    Permissions:
    - Must be authenticated
    - Only owners/managers can create/update/delete

    Functionality:
    - List all resource types
    - Create new custom types
    - Update existing types (except is_default flag)
    - Delete types (only if not default and not in use)
    """
    queryset = ResourceType.objects.all()
    serializer_class = ResourceTypeSerializer
    permission_classes = [AllowAny]  # TODO: Change to IsAuthenticated for production
    ordering = ['name']

    def destroy(self, request, *args, **kwargs):
        """Override destroy to add validation"""
        instance = self.get_object()

        # Check if default
        if instance.is_default:
            return Response(
                {'error': 'Cannot delete default resource types.'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Check if in use
        if instance.resources.exists():
            return Response(
                {
                    'error': f"Cannot delete resource type '{instance.name}' because it is in use by {instance.resources.count()} resource(s)."
                },
                status=status.HTTP_400_BAD_REQUEST
            )

        return super().destroy(request, *args, **kwargs)


class StaffRoleViewSet(TenantFilteredQuerySetMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing Staff Roles.

    Permissions:
    - Must be authenticated
    - Only owners/managers can access (staff denied via DenyStaffAllAccessPermission)

    Functionality:
    - GET /staff-roles/ - List all roles for tenant
    - POST /staff-roles/ - Create new role
    - GET /staff-roles/{id}/ - Get role details
    - PATCH /staff-roles/{id}/ - Update role
    - DELETE /staff-roles/{id}/ - Delete role (if no staff assigned and not default)
    - GET /staff-roles/available_permissions/ - Get all available permission keys

    NOTE: StaffRole is in the users app (SHARED_APP) with a tenant FK for scoping.
    We must explicitly filter by tenant since it doesn't use schema isolation.
    """
    queryset = StaffRole.objects.all()
    serializer_class = StaffRoleSerializer
    permission_classes = [IsAuthenticated, DenyStaffAllAccessPermission]
    ordering = ['-is_default', 'name']

    def filter_queryset_for_tenant(self, queryset):
        """
        Filter StaffRole by tenant FK since it's in a shared schema app.
        """
        request_tenant = getattr(self.request, 'tenant', None)
        if request_tenant:
            queryset = queryset.filter(tenant=request_tenant)
        else:
            # No tenant on request - return empty for safety
            return queryset.none()
        return queryset

    def get_queryset(self):
        """Filter by tenant and annotate with staff count"""
        queryset = super().get_queryset()
        from django.db.models import Count
        return queryset.annotate(staff_count=Count('staff_members'))

    def perform_create(self, serializer):
        """Set tenant on create"""
        tenant = getattr(self.request, 'tenant', None)
        if not tenant:
            raise PermissionDenied("Tenant context required to create staff roles.")
        serializer.save(tenant=tenant)

    def destroy(self, request, *args, **kwargs):
        """Override destroy to add validation"""
        instance = self.get_object()

        # Check if default
        if instance.is_default:
            return Response(
                {'error': 'Cannot delete default roles.'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Check if staff assigned
        staff_count = instance.staff_members.count()
        if staff_count > 0:
            return Response(
                {'error': f'Cannot delete role "{instance.name}" because {staff_count} staff member(s) are assigned to it.'},
                status=status.HTTP_400_BAD_REQUEST
            )

        return super().destroy(request, *args, **kwargs)

    @action(detail=False, methods=['get'])
    def available_permissions(self, request):
        """
        Return all available permission keys with their metadata.

        This endpoint provides the frontend with the full list of permission
        keys that can be configured on a staff role.
        """
        from smoothschedule.identity.users.staff_permissions import MENU_PERMISSIONS, DANGEROUS_PERMISSIONS

        return Response({
            'menu_permissions': MENU_PERMISSIONS,
            'dangerous_permissions': DANGEROUS_PERMISSIONS,
        })


class ResourceViewSet(TenantFilteredQuerySetMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing Resources.

    Permissions:
    - Must be authenticated
    - Staff members cannot access resources (owners/managers only)
    - Subject to MAX_RESOURCES quota (hard block on creation)

    The HasQuota permission prevents creating resources when tenant
    has reached their subscription tier limit.
    """
    queryset = Resource.objects.all()
    serializer_class = ResourceSerializer
    permission_classes = [IsAuthenticated, DenyStaffAllAccessPermission]

    # Mixin config: deny staff at queryset level too
    deny_staff_queryset = True

    filterset_fields = ['is_active', 'max_concurrent_events']
    search_fields = ['name', 'description']
    ordering_fields = ['name', 'created_at', 'max_concurrent_events']
    ordering = ['name']

    @action(detail=True, methods=['get'])
    def location(self, request, pk=None):
        """
        Get the latest location for a resource's linked staff member.

        GET /api/resources/{id}/location/

        Returns the most recent location update for the user linked to this resource,
        along with their current job status (if any).
        """
        from smoothschedule.communication.mobile.models import EmployeeLocationUpdate
        from django.contrib.contenttypes.models import ContentType

        resource = self.get_object()

        # Check if resource has a linked user
        if not resource.user:
            return Response({
                'has_location': False,
                'message': 'Resource has no linked user'
            })

        user = resource.user
        tenant = getattr(request, 'tenant', None)

        if not tenant:
            return Response({
                'has_location': False,
                'message': 'No tenant context'
            })

        # Get the latest location for this user
        latest_location = EmployeeLocationUpdate.objects.filter(
            tenant=tenant,
            employee=user
        ).order_by('-timestamp').first()

        if not latest_location:
            return Response({
                'has_location': False,
                'message': 'No location data available'
            })

        # Get the current active job for this user (EN_ROUTE or IN_PROGRESS)
        from django.db.models import Q
        user_ct = ContentType.objects.get_for_model(user.__class__)
        resource_ct = ContentType.objects.get_for_model(Resource)

        # Get resource IDs linked to this user
        user_resource_ids = list(
            Resource.objects.filter(user=user).values_list('id', flat=True)
        )

        # Find active events
        from schedule.models import Event, Participant
        active_statuses = ['EN_ROUTE', 'IN_PROGRESS']

        # Events where user is a participant
        user_participant_events = Participant.objects.filter(
            content_type=user_ct,
            object_id=user.id
        ).values_list('event_id', flat=True)

        # Events where user's resource is a participant
        resource_participant_events = Participant.objects.filter(
            content_type=resource_ct,
            object_id__in=user_resource_ids
        ).values_list('event_id', flat=True) if user_resource_ids else []

        all_event_ids = set(user_participant_events) | set(resource_participant_events)

        active_job = Event.objects.filter(
            id__in=all_event_ids,
            status__in=active_statuses
        ).order_by('-start_time').first()

        # Check if this location is recent (within last 10 minutes) and for an active job
        from django.utils import timezone
        from datetime import timedelta

        is_tracking = False
        if active_job and latest_location.event_id == active_job.id:
            time_since_update = timezone.now() - latest_location.timestamp
            is_tracking = time_since_update < timedelta(minutes=10)

        return Response({
            'has_location': True,
            'latitude': float(latest_location.latitude),
            'longitude': float(latest_location.longitude),
            'accuracy': latest_location.accuracy,
            'heading': latest_location.heading,
            'speed': latest_location.speed,
            'timestamp': latest_location.timestamp.isoformat(),
            'is_tracking': is_tracking,
            'active_job': {
                'id': active_job.id,
                'title': active_job.title,
                'status': active_job.status,
                'status_display': active_job.get_status_display(),
            } if active_job else None,
        })


class EventViewSet(TenantFilteredQuerySetMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing Events.

    Permissions:
    - Must be authenticated
    - Staff members can only view/modify events they are assigned to

    Validation:
    - EventSerializer.validate() automatically checks resource availability
    - If resource capacity exceeded, returns 400 Bad Request
    - See schedule/services.py AvailabilityService for logic

    Query Parameters:
    - start_date: Filter events starting on or after this date (ISO format)
    - end_date: Filter events starting before this date (ISO format)
    - status: Filter by event status
    - assigned_to: Filter by assigned staff ('me' for current user)
    - resource: Filter by resource ID

    Actions:
    - POST /api/events/{id}/set_status/ - Change event status with validation
    - POST /api/events/{id}/start_en_route/ - Start traveling to job
    - GET /api/events/{id}/status_history/ - Get status change history
    - GET /api/events/{id}/allowed_transitions/ - Get valid next statuses
    """
    queryset = Event.objects.all()
    serializer_class = EventSerializer
    permission_classes = [IsAuthenticated]

    filterset_fields = ['status']
    search_fields = ['title', 'notes']
    ordering_fields = ['start_time', 'end_time', 'created_at']
    ordering = ['start_time']

    def _get_staff_assigned_events(self, user, queryset):
        """
        Filter queryset to events where user is assigned as staff.

        Returns events where:
        - User is a participant with STAFF/RESOURCE role, OR
        - User's linked Resource is a participant
        """
        from django.contrib.contenttypes.models import ContentType

        user_ct = ContentType.objects.get_for_model(User)
        resource_ct = ContentType.objects.get_for_model(Resource)

        # Get resource IDs linked to this user
        user_resource_ids = list(
            Resource.objects.filter(user=user).values_list('id', flat=True)
        )

        # Find events where user is directly a participant
        user_event_ids = Participant.objects.filter(
            content_type=user_ct,
            object_id=user.id,
            role__in=[Participant.Role.STAFF, Participant.Role.RESOURCE]
        ).values_list('event_id', flat=True)

        # Find events where user's resource is a participant
        resource_event_ids = []
        if user_resource_ids:
            resource_event_ids = Participant.objects.filter(
                content_type=resource_ct,
                object_id__in=user_resource_ids,
                role__in=[Participant.Role.STAFF, Participant.Role.RESOURCE]
            ).values_list('event_id', flat=True)

        # Combine event IDs
        all_event_ids = set(user_event_ids) | set(resource_event_ids)

        return queryset.filter(id__in=all_event_ids)

    def filter_queryset_for_tenant(self, queryset):
        """
        Apply event-specific filtering after tenant validation.

        - Customers only see events where they are a participant
        - Staff only see events where they are assigned
        - Managers/Owners see all events by default, can filter with assigned_to=me
        - Supports date range and resource filtering via query params
        """
        from django.contrib.contenttypes.models import ContentType
        user = self.request.user

        # Filter by user role
        if user.role == User.Role.CUSTOMER:
            # Customers only see their own events
            user_content_type = ContentType.objects.get_for_model(User)
            participant_event_ids = Participant.objects.filter(
                content_type=user_content_type,
                object_id=user.id
            ).values_list('event_id', flat=True)
            queryset = queryset.filter(id__in=participant_event_ids)
        elif user.role == User.Role.TENANT_STAFF:
            # Staff ALWAYS see only their assigned events
            queryset = self._get_staff_assigned_events(user, queryset)
        else:
            # Managers/Owners - check for assigned_to filter
            assigned_to = self.request.query_params.get('assigned_to')
            if assigned_to == 'me':
                queryset = self._get_staff_assigned_events(user, queryset)

        # Filter by specific resource ID
        resource_id = self.request.query_params.get('resource')
        if resource_id:
            resource_ct = ContentType.objects.get_for_model(Resource)
            resource_event_ids = Participant.objects.filter(
                content_type=resource_ct,
                object_id=resource_id
            ).values_list('event_id', flat=True)
            queryset = queryset.filter(id__in=resource_event_ids)

        # Filter by date range
        start_date = self.request.query_params.get('start_date')
        end_date = self.request.query_params.get('end_date')

        if start_date:
            from django.utils.dateparse import parse_datetime
            start_dt = parse_datetime(start_date)
            if start_dt:
                queryset = queryset.filter(start_time__gte=start_dt)

        if end_date:
            from django.utils.dateparse import parse_datetime
            end_dt = parse_datetime(end_date)
            if end_dt:
                queryset = queryset.filter(start_time__lt=end_dt)

        return queryset

    def perform_create(self, serializer):
        """
        Create event with automatic availability validation.

        The EventSerializer.validate() method calls AvailabilityService
        to check if resources have capacity. If not, DRF automatically
        returns 400 Bad Request with error details.
        """
        serializer.save(created_by=self.request.user)

    def perform_update(self, serializer):
        """
        Update event with availability re-validation.

        Uses exclude_event_id in AvailabilityService to allow
        rescheduling of the event itself.
        """
        serializer.save()

    @action(detail=True, methods=['post'])
    def set_status(self, request, pk=None):
        """
        Update an event's status with validation.

        POST /api/events/{id}/set_status/

        Body:
        {
            "status": "IN_PROGRESS",
            "notes": "Optional notes",
            "latitude": 40.7128,
            "longitude": -74.0060,
            "skip_notifications": false
        }

        Validates the status transition using StatusMachine rules.
        Records status change in history and optionally triggers customer notifications.
        """
        from smoothschedule.communication.mobile.services import StatusMachine
        from smoothschedule.communication.mobile.services.status_machine import StatusTransitionError

        event = self.get_object()
        tenant = getattr(request, 'tenant', None)

        if not tenant:
            return Response(
                {'error': 'No tenant context'},
                status=status.HTTP_400_BAD_REQUEST
            )

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

        notes = request.data.get('notes', '')
        latitude = request.data.get('latitude')
        longitude = request.data.get('longitude')
        skip_notifications = request.data.get('skip_notifications', False)

        status_machine = StatusMachine(tenant, request.user)

        try:
            event = status_machine.transition(
                event=event,
                new_status=new_status,
                notes=notes,
                latitude=latitude,
                longitude=longitude,
                source='web_app',
                skip_notifications=skip_notifications,
            )

            serializer = self.get_serializer(event)
            return Response({
                'success': True,
                'event': serializer.data,
            })

        except StatusTransitionError as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_400_BAD_REQUEST
            )

    @action(detail=True, methods=['post'])
    def start_en_route(self, request, pk=None):
        """
        Start traveling to a job (convenience action for EN_ROUTE status).

        POST /api/events/{id}/start_en_route/

        Body:
        {
            "latitude": 40.7128,
            "longitude": -74.0060,
            "send_customer_notification": true
        }

        Changes status to EN_ROUTE and optionally notifies customer.
        """
        from smoothschedule.communication.mobile.services import StatusMachine
        from smoothschedule.communication.mobile.services.status_machine import StatusTransitionError

        event = self.get_object()
        tenant = getattr(request, 'tenant', None)

        if not tenant:
            return Response(
                {'error': 'No tenant context'},
                status=status.HTTP_400_BAD_REQUEST
            )

        latitude = request.data.get('latitude')
        longitude = request.data.get('longitude')
        send_notification = request.data.get('send_customer_notification', True)

        status_machine = StatusMachine(tenant, request.user)

        try:
            event = status_machine.transition(
                event=event,
                new_status=Event.Status.EN_ROUTE,
                latitude=latitude,
                longitude=longitude,
                source='web_app',
                skip_notifications=not send_notification,
            )

            serializer = self.get_serializer(event)
            return Response({
                'success': True,
                'event': serializer.data,
                'tracking_enabled': True,
            })

        except StatusTransitionError as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_400_BAD_REQUEST
            )

    @action(detail=True, methods=['get'])
    def status_history(self, request, pk=None):
        """
        Get the status change history for an event.

        GET /api/events/{id}/status_history/

        Returns list of status changes with timestamps and who made them.
        """
        from smoothschedule.communication.mobile.models import EventStatusHistory

        event = self.get_object()
        tenant = getattr(request, 'tenant', None)

        if not tenant:
            return Response(
                {'error': 'No tenant context'},
                status=status.HTTP_400_BAD_REQUEST
            )

        history = EventStatusHistory.objects.filter(
            tenant=tenant,
            event_id=event.id
        ).select_related('changed_by').order_by('-changed_at')[:50]

        return Response({
            'event_id': event.id,
            'history': [
                {
                    'old_status': h.old_status,
                    'new_status': h.new_status,
                    'changed_by': h.changed_by.full_name if h.changed_by else None,
                    'changed_at': h.changed_at.isoformat(),
                    'notes': h.notes,
                    'source': h.source,
                    'latitude': float(h.latitude) if h.latitude else None,
                    'longitude': float(h.longitude) if h.longitude else None,
                }
                for h in history
            ]
        })

    @action(detail=True, methods=['get'])
    def allowed_transitions(self, request, pk=None):
        """
        Get the valid status transitions for an event.

        GET /api/events/{id}/allowed_transitions/

        Returns list of statuses the event can transition to.
        """
        from smoothschedule.communication.mobile.services import StatusMachine

        event = self.get_object()
        tenant = getattr(request, 'tenant', None)

        if not tenant:
            return Response(
                {'error': 'No tenant context'},
                status=status.HTTP_400_BAD_REQUEST
            )

        status_machine = StatusMachine(tenant, request.user)
        allowed = status_machine.get_allowed_transitions(event)

        # Include display names
        status_choices = dict(Event.Status.choices)

        return Response({
            'event_id': event.id,
            'current_status': event.status,
            'current_status_display': event.get_status_display(),
            'allowed_transitions': [
                {
                    'status': s,
                    'display': status_choices.get(s, s)
                }
                for s in allowed
            ]
        })


class ParticipantViewSet(TenantFilteredQuerySetMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing Event Participants.

    Allows adding/removing participants (Resources, Staff, Customers)
    to/from events via the GenericForeignKey pattern.
    """
    queryset = Participant.objects.all()
    serializer_class = ParticipantSerializer
    permission_classes = [IsAuthenticated]

    filterset_fields = ['event', 'role', 'content_type']
    ordering_fields = ['created_at']
    ordering = ['-created_at']


class CustomerViewSet(UserTenantFilteredMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing Customers.

    Customers are Users with role=CUSTOMER belonging to the current tenant.

    Permissions:
    - Staff members cannot list customers
    - Staff can only retrieve individual customers with limited fields (name, address)
    """
    queryset = User.objects.filter(role=User.Role.CUSTOMER)
    serializer_class = CustomerSerializer
    permission_classes = [IsAuthenticated, DenyStaffListPermission]

    filterset_fields = ['is_active']
    search_fields = ['email', 'first_name', 'last_name']
    ordering_fields = ['email', 'created_at']
    ordering = ['email']

    def retrieve(self, request, *args, **kwargs):
        """
        Retrieve customer - staff get limited fields only.
        """
        instance = self.get_object()
        if request.user.role == User.Role.TENANT_STAFF:
            # Return only name and address for staff
            return Response({
                'id': instance.id,
                'name': instance.full_name,
                'first_name': instance.first_name,
                'last_name': instance.last_name,
                'city': getattr(instance, 'city', ''),
                'state': getattr(instance, 'state', ''),
                'zip': getattr(instance, 'zip', ''),
            })
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

    def filter_queryset_for_tenant(self, queryset):
        """Apply customer-specific filtering."""
        queryset = super().filter_queryset_for_tenant(queryset)

        # Apply status filter if provided
        status_filter = self.request.query_params.get('status')
        if status_filter:
            if status_filter == 'Active':
                queryset = queryset.filter(is_active=True)
            elif status_filter == 'Inactive':
                queryset = queryset.filter(is_active=False)

        # Apply search filter if provided
        search = self.request.query_params.get('search')
        if search:
            from django.db.models import Q
            queryset = queryset.filter(
                Q(email__icontains=search) |
                Q(first_name__icontains=search) |
                Q(last_name__icontains=search)
            )

        return queryset

    def perform_create(self, serializer):
        """
        Set sandbox mode and tenant when creating a new customer.
        """
        is_sandbox = getattr(self.request, 'sandbox_mode', False)
        tenant = None
        if self.request.user.is_authenticated and self.request.user.tenant:
            tenant = self.request.user.tenant
        serializer.save(
            role=User.Role.CUSTOMER,
            is_sandbox=is_sandbox,
            tenant=tenant,
        )


class ServiceViewSet(TenantFilteredQuerySetMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing Services.

    Services are the offerings a business provides (e.g., Haircut, Massage).

    Permissions:
    - Must be authenticated
    - Staff members cannot access services
    - Subject to MAX_SERVICES quota (hard block on creation)
    """
    queryset = Service.objects.all()
    serializer_class = ServiceSerializer
    permission_classes = [IsAuthenticated, DenyStaffAllAccessPermission, HasQuota('MAX_SERVICES')]

    # Mixin config: deny staff at queryset level too
    deny_staff_queryset = True

    filterset_fields = ['is_active']
    search_fields = ['name', 'description']
    ordering_fields = ['name', 'price', 'duration', 'display_order', 'created_at']
    ordering = ['display_order', 'name']

    def filter_queryset_for_tenant(self, queryset):
        """Filter services, optionally including inactive ones."""
        # By default only show active services
        show_inactive = self.request.query_params.get('show_inactive', 'false')
        if show_inactive.lower() != 'true':
            queryset = queryset.filter(is_active=True)
        return queryset

    @action(detail=False, methods=['post'])
    def reorder(self, request):
        """
        Bulk update service display order.

        Expects: { "order": [1, 3, 2, 5, 4] }
        Where the list contains service IDs in the desired display order.
        """
        order = request.data.get('order', [])

        if not isinstance(order, list):
            return Response(
                {'error': 'order must be a list of service IDs'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Update display_order for each service
        for index, service_id in enumerate(order):
            Service.objects.filter(id=service_id).update(display_order=index)

        return Response({'status': 'ok', 'updated': len(order)})


class StaffViewSet(UserTenantFilteredMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing staff members (Users who can be assigned to resources).

    Staff members are Users with roles: TENANT_OWNER, TENANT_MANAGER, TENANT_STAFF.

    Supports:
    - GET /api/staff/ - List staff members
    - GET /api/staff/{id}/ - Get staff member details
    - PATCH /api/staff/{id}/ - Update staff member (is_active, permissions)
    - POST /api/staff/{id}/toggle_active/ - Toggle active status
    """
    serializer_class = StaffSerializer
    permission_classes = [IsAuthenticated]

    search_fields = ['email', 'first_name', 'last_name']
    ordering_fields = ['email', 'first_name', 'last_name']
    ordering = ['first_name', 'last_name']

    # Disable create and delete - staff are managed via invitations
    # Note: 'post' is needed for custom actions like toggle_active
    http_method_names = ['get', 'patch', 'post', 'head', 'options']

    def get_queryset(self):
        """
        Return staff members for the current tenant.

        Staff are Users with roles: TENANT_OWNER, TENANT_MANAGER, TENANT_STAFF.
        """
        from django.db.models import Q

        # Set base queryset to staff roles only
        self.queryset = User.objects.filter(
            Q(role=User.Role.TENANT_OWNER) |
            Q(role=User.Role.TENANT_MANAGER) |
            Q(role=User.Role.TENANT_STAFF)
        )

        # Apply mixin filtering (tenant + sandbox)
        return super().get_queryset()

    def filter_queryset_for_tenant(self, queryset):
        """Apply staff-specific filtering."""
        queryset = super().filter_queryset_for_tenant(queryset)
        from django.db.models import Q

        # Include inactive staff by default (so admins can reactivate them)
        show_inactive = self.request.query_params.get('show_inactive', 'true')
        if show_inactive.lower() != 'true':
            queryset = queryset.filter(is_active=True)

        # Apply search filter if provided
        search = self.request.query_params.get('search')
        if search:
            queryset = queryset.filter(
                Q(email__icontains=search) |
                Q(first_name__icontains=search) |
                Q(last_name__icontains=search)
            )

        return queryset

    def partial_update(self, request, *args, **kwargs):
        """
        Update staff member.

        Allowed fields: is_active, permissions

        Owners can edit any staff member.
        Managers can only edit staff (not other managers or owners).
        """
        instance = self.get_object()

        # TODO: Add permission checks when authentication is enabled
        # current_user = request.user
        # if current_user.role == User.Role.TENANT_MANAGER:
        #     if instance.role in [User.Role.TENANT_OWNER, User.Role.TENANT_MANAGER]:
        #         return Response(
        #             {'error': 'Managers cannot edit owners or other managers.'},
        #             status=status.HTTP_403_FORBIDDEN
        #         )

        # Only allow updating specific fields
        allowed_fields = {'is_active', 'permissions'}
        update_data = {k: v for k, v in request.data.items() if k in allowed_fields}

        serializer = self.get_serializer(instance, data=update_data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def toggle_active(self, request, pk=None):
        """Toggle the active status of a staff member."""
        staff = self.get_object()

        # Prevent deactivating yourself
        # TODO: Enable this check when authentication is enabled
        # if request.user.id == staff.id:
        #     return Response(
        #         {'error': 'You cannot deactivate your own account.'},
        #         status=status.HTTP_400_BAD_REQUEST
        #     )

        staff.is_active = not staff.is_active
        staff.save(update_fields=['is_active'])

        return Response({
            'id': staff.id,
            'is_active': staff.is_active,
            'message': f"Staff member {'activated' if staff.is_active else 'deactivated'} successfully."
        })


class ScheduledTaskViewSet(TaskFeatureRequiredMixin, TenantFilteredQuerySetMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing scheduled tasks.

    Permissions:
    - Must be authenticated
    - Only owners/managers can create/update/delete
    - Subject to MAX_AUTOMATED_TASKS quota (hard block on creation)
    - Requires can_use_automations AND can_use_tasks features

    Features:
    - List all scheduled tasks
    - Create new scheduled tasks
    - Update existing tasks
    - Delete tasks
    - Pause/resume tasks
    - Trigger manual execution
    - View execution logs
    """
    queryset = ScheduledTask.objects.all()
    serializer_class = ScheduledTaskSerializer
    permission_classes = [IsAuthenticated, DenyStaffAllAccessPermission, HasQuota('MAX_AUTOMATED_TASKS')]
    ordering = ['-created_at']

    # Mixin config: deny staff at queryset level
    deny_staff_queryset = True

    @action(detail=True, methods=['post'])
    def pause(self, request, pk=None):
        """Pause a scheduled task"""
        task = self.get_object()

        if task.status == ScheduledTask.Status.PAUSED:
            return Response(
                {'error': 'Task is already paused'},
                status=status.HTTP_400_BAD_REQUEST
            )

        task.status = ScheduledTask.Status.PAUSED
        task.save(update_fields=['status'])

        return Response({
            'id': task.id,
            'status': task.status,
            'message': 'Task paused successfully'
        })

    @action(detail=True, methods=['post'])
    def resume(self, request, pk=None):
        """Resume a paused scheduled task"""
        task = self.get_object()

        if task.status != ScheduledTask.Status.PAUSED:
            return Response(
                {'error': 'Task is not paused'},
                status=status.HTTP_400_BAD_REQUEST
            )

        task.status = ScheduledTask.Status.ACTIVE
        task.update_next_run_time()
        task.save(update_fields=['status'])

        return Response({
            'id': task.id,
            'status': task.status,
            'next_run_at': task.next_run_at,
            'message': 'Task resumed successfully'
        })

    @action(detail=True, methods=['post'])
    def execute(self, request, pk=None):
        """Manually trigger task execution"""
        task = self.get_object()

        # Import here to avoid circular dependency
        from .tasks import execute_scheduled_task

        # Queue the task for immediate execution
        result = execute_scheduled_task.delay(task.id)

        return Response({
            'id': task.id,
            'celery_task_id': result.id,
            'message': 'Task queued for execution'
        })

    @action(detail=True, methods=['get'])
    def logs(self, request, pk=None):
        """Get execution logs for this task"""
        task = self.get_object()

        # Get pagination parameters
        limit = int(request.query_params.get('limit', 20))
        offset = int(request.query_params.get('offset', 0))

        logs = task.execution_logs.all()[offset:offset + limit]
        serializer = TaskExecutionLogSerializer(logs, many=True)

        return Response({
            'count': task.execution_logs.count(),
            'results': serializer.data
        })


class TaskExecutionLogViewSet(viewsets.ReadOnlyModelViewSet):
    """
    API endpoint for viewing task execution logs (read-only).

    Features:
    - List all execution logs
    - Filter by task, status, date range
    - View individual log details
    """
    queryset = TaskExecutionLog.objects.select_related('scheduled_task').all()
    serializer_class = TaskExecutionLogSerializer
    permission_classes = [AllowAny]  # TODO: Change to IsAuthenticated for production
    ordering = ['-started_at']

    def get_queryset(self):
        """Filter logs by query parameters"""
        queryset = super().get_queryset()

        # Filter by scheduled task
        task_id = self.request.query_params.get('task_id')
        if task_id:
            queryset = queryset.filter(scheduled_task_id=task_id)

        # Filter by status
        status_filter = self.request.query_params.get('status')
        if status_filter:
            queryset = queryset.filter(status=status_filter)

        return queryset


class PluginViewSet(viewsets.ViewSet):
    """
    API endpoint for listing available plugins.

    Features:
    - List all registered plugins
    - Get plugin details
    - List plugins by category
    """
    permission_classes = [AllowAny]  # TODO: Change to IsAuthenticated for production

    def list(self, request):
        """List all available plugins"""
        from smoothschedule.scheduling.automations.registry import registry

        plugins = registry.list_all()
        serializer = PluginInfoSerializer(plugins, many=True)

        return Response(serializer.data)

    @action(detail=False, methods=['get'])
    def by_category(self, request):
        """List plugins grouped by category"""
        from smoothschedule.scheduling.automations.registry import registry

        plugins_by_category = registry.list_by_category()

        return Response(plugins_by_category)

    def retrieve(self, request, pk=None):
        """Get details for a specific plugin"""
        from smoothschedule.scheduling.automations.registry import registry

        plugin_class = registry.get(pk)
        if not plugin_class:
            return Response(
                {'error': f"Plugin '{pk}' not found"},
                status=status.HTTP_404_NOT_FOUND
            )

        plugin_info = {
            'name': plugin_class.name,
            'display_name': plugin_class.display_name,
            'description': plugin_class.description,
            'category': plugin_class.category,
            'config_schema': plugin_class.config_schema,
        }

        serializer = PluginInfoSerializer(plugin_info)
        return Response(serializer.data)


class PluginTemplateViewSet(viewsets.ModelViewSet):
    """
    API endpoint for managing plugin templates.

    Features:
    - List all plugin templates (filtered by visibility)
    - Create new plugin templates
    - Update existing templates
    - Delete templates
    - Publish to marketplace
    - Unpublish from marketplace
    - Install a template as a ScheduledTask
    - Request approval (for marketplace publishing)
    - Approve/reject templates (platform admins only)

    Permissions:
    - Marketplace view: Always accessible (for discovery)
    - My Plugins view: Requires can_use_automations feature
    - Install action: Requires can_use_automations feature
    - Create: Requires can_use_automations AND can_create_automations features
    """
    queryset = PluginTemplate.objects.all()
    serializer_class = PluginTemplateSerializer
    permission_classes = [AllowAny]  # TODO: Change to IsAuthenticated for production
    ordering = ['-created_at']
    filterset_fields = ['visibility', 'category', 'is_approved']
    search_fields = ['name', 'short_description', 'description', 'tags']

    def _has_plugins_permission(self):
        """Check if tenant has permission to use plugins."""
        tenant = getattr(self.request, 'tenant', None)
        if tenant:
            return tenant.has_feature('can_use_automations')
        return True  # Allow if no tenant context

    def get_queryset(self):
        """
        Filter templates based on user permissions.

        - Marketplace view: Only approved PUBLIC templates (always accessible)
        - My Plugins: User's own templates (requires can_use_automations)
        - Platform admins: All templates
        """
        queryset = super().get_queryset()
        view_mode = self.request.query_params.get('view', 'marketplace')

        if view_mode == 'marketplace':
            # Public marketplace - platform official + approved public templates
            # Always accessible for discovery/marketing purposes
            from django.db.models import Q
            queryset = queryset.filter(
                Q(visibility=PluginTemplate.Visibility.PLATFORM) |
                Q(visibility=PluginTemplate.Visibility.PUBLIC, is_approved=True)
            )
        elif view_mode == 'my_plugins':
            # User's own templates - requires plugin permission
            if not self._has_plugins_permission():
                queryset = queryset.none()
            elif self.request.user.is_authenticated:
                queryset = queryset.filter(author=self.request.user)
            else:
                queryset = queryset.none()
        elif view_mode == 'platform':
            # Platform official plugins - always accessible for discovery
            queryset = queryset.filter(visibility=PluginTemplate.Visibility.PLATFORM)
        # else: all templates (for platform admins)

        # Filter by category if provided
        category = self.request.query_params.get('category')
        if category:
            queryset = queryset.filter(category=category)

        # Filter by search query
        search = self.request.query_params.get('search')
        if search:
            from django.db.models import Q
            queryset = queryset.filter(
                Q(name__icontains=search) |
                Q(short_description__icontains=search) |
                Q(description__icontains=search) |
                Q(tags__icontains=search)
            )

        return queryset

    def get_serializer_class(self):
        """Use lightweight serializer for list view"""
        if self.action == 'list':
            return PluginTemplateListSerializer
        return PluginTemplateSerializer

    def perform_create(self, serializer):
        """Set author and extract template variables on create"""
        from .template_parser import TemplateVariableParser
        from rest_framework.exceptions import PermissionDenied

        # Check permission to use plugins first
        tenant = getattr(self.request, 'tenant', None)
        if tenant and not tenant.has_feature('can_use_automations'):
            raise PermissionDenied(
                "Your current plan does not include Plugin access. "
                "Please upgrade your subscription to use plugins."
            )

        # Check permission to create plugins (requires can_use_automations)
        if tenant and not tenant.has_feature('can_create_automations'):
            raise PermissionDenied(
                "Your current plan does not include Plugin Creation. "
                "Please upgrade your subscription to create custom plugins."
            )

        plugin_code = serializer.validated_data.get('plugin_code', '')
        template_vars = TemplateVariableParser.extract_variables(plugin_code)

        # Convert to dict format expected by model
        template_vars_dict = {var['name']: var for var in template_vars}

        serializer.save(
            author=self.request.user if self.request.user.is_authenticated else None,
            template_variables=template_vars_dict
        )

    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        """Publish template to marketplace (requires approval)"""
        template = self.get_object()

        # Check ownership
        if template.author != request.user:
            return Response(
                {'error': 'You can only publish your own templates'},
                status=status.HTTP_403_FORBIDDEN
            )

        # Check if approved
        if not template.is_approved:
            return Response(
                {'error': 'Template must be approved before publishing to marketplace'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Publish
        try:
            template.publish_to_marketplace(request.user)
            return Response({
                'message': 'Template published to marketplace successfully',
                'slug': template.slug
            })
        except DjangoValidationError as e:
            return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=True, methods=['post'])
    def unpublish(self, request, pk=None):
        """Unpublish template from marketplace"""
        template = self.get_object()

        # Check ownership
        if template.author != request.user:
            return Response(
                {'error': 'You can only unpublish your own templates'},
                status=status.HTTP_403_FORBIDDEN
            )

        template.unpublish_from_marketplace()
        return Response({
            'message': 'Template unpublished from marketplace successfully'
        })

    @action(detail=True, methods=['post'])
    def install(self, request, pk=None):
        """
        Install a plugin template as a ScheduledTask.

        Expects:
        {
            "name": "Task Name",
            "description": "Task Description",
            "config_values": {"variable1": "value1", ...},
            "schedule_type": "CRON",
            "cron_expression": "0 0 * * *"
        }
        """
        # Check permission to use plugins
        tenant = getattr(request, 'tenant', None)
        if tenant and not tenant.has_feature('can_use_automations'):
            return Response(
                {'error': 'Your current plan does not include Plugin access. Please upgrade your subscription to install plugins.'},
                status=status.HTTP_403_FORBIDDEN
            )

        template = self.get_object()

        # Check if template is accessible
        if template.visibility == PluginTemplate.Visibility.PRIVATE:
            if not request.user.is_authenticated or template.author != request.user:
                return Response(
                    {'error': 'This template is private'},
                    status=status.HTTP_403_FORBIDDEN
                )
        elif template.visibility == PluginTemplate.Visibility.PUBLIC:
            if not template.is_approved:
                return Response(
                    {'error': 'This template has not been approved'},
                    status=status.HTTP_400_BAD_REQUEST
                )

        # Create ScheduledTask from template
        from .template_parser import TemplateVariableParser

        name = request.data.get('name')
        description = request.data.get('description', '')
        config_values = request.data.get('config_values', {})
        schedule_type = request.data.get('schedule_type')
        cron_expression = request.data.get('cron_expression')
        interval_minutes = request.data.get('interval_minutes')
        run_at = request.data.get('run_at')

        if not name:
            return Response(
                {'error': 'name is required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Compile template with config values
        try:
            compiled_code = TemplateVariableParser.compile_template(
                template.plugin_code,
                config_values,
                context={}  # TODO: Add business context
            )
        except ValueError as e:
            return Response(
                {'error': f'Configuration error: {str(e)}'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Create ScheduledTask
        scheduled_task = ScheduledTask.objects.create(
            name=name,
            description=description,
            plugin_name='custom_script',  # Use custom script plugin
            plugin_code=compiled_code,
            plugin_config={},
            schedule_type=schedule_type,
            cron_expression=cron_expression,
            interval_minutes=interval_minutes,
            run_at=run_at,
            status=ScheduledTask.Status.ACTIVE,
            created_by=request.user if request.user.is_authenticated else None
        )

        # Create PluginInstallation record
        installation = PluginInstallation.objects.create(
            template=template,
            scheduled_task=scheduled_task,
            installed_by=request.user if request.user.is_authenticated else None,
            config_values=config_values,
            template_version_hash=template.plugin_code_hash
        )

        # Increment install count
        template.install_count += 1
        template.save(update_fields=['install_count'])

        return Response({
            'message': 'Plugin installed successfully',
            'scheduled_task_id': scheduled_task.id,
            'installation_id': installation.id
        }, status=status.HTTP_201_CREATED)

    @action(detail=True, methods=['post'])
    def request_approval(self, request, pk=None):
        """Request approval for marketplace publishing"""
        template = self.get_object()

        # Check ownership
        if template.author != request.user:
            return Response(
                {'error': 'You can only request approval for your own templates'},
                status=status.HTTP_403_FORBIDDEN
            )

        # Check if already approved or pending
        if template.is_approved:
            return Response(
                {'error': 'Template is already approved'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Validate plugin code
        validation = template.can_be_published()
        if not validation:
            from .safe_scripting import validate_plugin_whitelist
            errors = validate_plugin_whitelist(template.plugin_code)
            return Response(
                {'error': 'Template has validation errors', 'errors': errors['errors']},
                status=status.HTTP_400_BAD_REQUEST
            )

        # TODO: Notify platform admins about approval request
        # For now, just return success
        return Response({
            'message': 'Approval requested successfully. A platform administrator will review your plugin.',
            'template_id': template.id
        })

    @action(detail=True, methods=['post'])
    def approve(self, request, pk=None):
        """Approve template for marketplace (platform admins only)"""
        # TODO: Add permission check for platform admins
        # if not request.user.has_perm('can_approve_plugins'):
        #     return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN)

        template = self.get_object()

        if template.is_approved:
            return Response(
                {'error': 'Template is already approved'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Validate plugin code
        from .safe_scripting import validate_plugin_whitelist
        validation = validate_plugin_whitelist(template.plugin_code, scheduled_task=None)

        if not validation['valid']:
            return Response(
                {'error': 'Template has validation errors', 'errors': validation['errors']},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Approve
        from django.utils import timezone
        template.is_approved = True
        template.approved_by = request.user if request.user.is_authenticated else None
        template.approved_at = timezone.now()
        template.rejection_reason = ''
        template.save()

        return Response({
            'message': 'Template approved successfully',
            'template_id': template.id
        })

    @action(detail=True, methods=['post'])
    def reject(self, request, pk=None):
        """Reject template for marketplace (platform admins only)"""
        # TODO: Add permission check for platform admins
        # if not request.user.has_perm('can_approve_plugins'):
        #     return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN)

        template = self.get_object()
        reason = request.data.get('reason', 'No reason provided')

        template.is_approved = False
        template.rejection_reason = reason
        template.save()

        return Response({
            'message': 'Template rejected',
            'reason': reason
        })


class PluginInstallationViewSet(viewsets.ModelViewSet):
    """
    API endpoint for managing plugin installations.

    Features:
    - List user's installed plugins
    - View installation details
    - Update installation (update to latest version)
    - Uninstall plugin
    - Rate and review plugin

    Permissions:
    - Requires can_use_automations feature for all operations
    """
    queryset = PluginInstallation.objects.select_related('template', 'scheduled_task').all()
    serializer_class = PluginInstallationSerializer
    permission_classes = [AllowAny]  # TODO: Change to IsAuthenticated for production
    ordering = ['-installed_at']

    def _check_plugins_permission(self):
        """Check if tenant has permission to access plugin installations."""
        from rest_framework.exceptions import PermissionDenied

        tenant = getattr(self.request, 'tenant', None)
        if tenant and not tenant.has_feature('can_use_automations'):
            raise PermissionDenied(
                "Your current plan does not include Plugin access. "
                "Please upgrade your subscription to use plugins."
            )

    def list(self, request, *args, **kwargs):
        """List plugin installations with permission check."""
        self._check_plugins_permission()
        return super().list(request, *args, **kwargs)

    def retrieve(self, request, *args, **kwargs):
        """Retrieve a plugin installation with permission check."""
        self._check_plugins_permission()
        return super().retrieve(request, *args, **kwargs)

    def get_queryset(self):
        """Return installations for current user/tenant"""
        queryset = super().get_queryset()

        # TODO: Filter by tenant when multi-tenancy is fully enabled
        # if self.request.user.is_authenticated and self.request.user.tenant:
        #     queryset = queryset.filter(scheduled_task__tenant=self.request.user.tenant)

        return queryset

    def perform_create(self, serializer):
        """Check permission to use plugins before installing"""
        from rest_framework.exceptions import PermissionDenied

        # Check permission to use plugins
        tenant = getattr(self.request, 'tenant', None)
        if tenant and not tenant.has_feature('can_use_automations'):
            raise PermissionDenied(
                "Your current plan does not include Plugin access. "
                "Please upgrade your subscription to use plugins."
            )

        serializer.save()

    @action(detail=True, methods=['post'])
    def update_to_latest(self, request, pk=None):
        """Update installed plugin to latest template version"""
        installation = self.get_object()

        if not installation.has_update_available():
            return Response(
                {'error': 'No update available'},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            installation.update_to_latest()
            return Response({
                'message': 'Plugin updated successfully',
                'new_version_hash': installation.template_version_hash
            })
        except DjangoValidationError as e:
            return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=True, methods=['post'])
    def rate(self, request, pk=None):
        """Rate an installed plugin"""
        installation = self.get_object()
        rating = request.data.get('rating')
        review = request.data.get('review', '')

        if not rating or not isinstance(rating, int) or rating < 1 or rating > 5:
            return Response(
                {'error': 'Rating must be an integer between 1 and 5'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Update installation
        from django.utils import timezone
        installation.rating = rating
        installation.review = review
        installation.reviewed_at = timezone.now()
        installation.save()

        # Update template average rating
        if installation.template:
            template = installation.template
            ratings = PluginInstallation.objects.filter(
                template=template,
                rating__isnull=False
            ).values_list('rating', flat=True)

            if ratings:
                from decimal import Decimal
                template.rating_average = Decimal(sum(ratings)) / Decimal(len(ratings))
                template.rating_count = len(ratings)
                template.save(update_fields=['rating_average', 'rating_count'])

        return Response({
            'message': 'Rating submitted successfully',
            'rating': rating
        })

    def destroy(self, request, *args, **kwargs):
        """Uninstall plugin (delete ScheduledTask and Installation)"""
        installation = self.get_object()

        # Delete the scheduled task (this will cascade delete the installation)
        if installation.scheduled_task:
            installation.scheduled_task.delete()
        else:
            # If scheduled task was already deleted, just delete the installation
            installation.delete()

        return Response({
            'message': 'Plugin uninstalled successfully'
        }, status=status.HTTP_204_NO_CONTENT)


class EventPluginViewSet(viewsets.ModelViewSet):
    """
    API endpoint for managing plugins attached to calendar events.

    This allows users to attach installed plugins to events with configurable
    timing triggers (before start, at start, after end, on complete, etc.)

    Endpoints:
    - GET /api/event-plugins/?event_id=X - List plugins for an event
    - POST /api/event-plugins/ - Attach plugin to event
    - PATCH /api/event-plugins/{id}/ - Update timing/trigger
    - DELETE /api/event-plugins/{id}/ - Remove plugin from event
    - POST /api/event-plugins/{id}/toggle/ - Enable/disable plugin
    """
    queryset = EventPlugin.objects.select_related(
        'event',
        'plugin_installation',
        'plugin_installation__template'
    ).all()
    serializer_class = EventPluginSerializer
    permission_classes = [AllowAny]  # TODO: Change to IsAuthenticated

    def get_queryset(self):
        """Filter by event if specified"""
        queryset = super().get_queryset()

        event_id = self.request.query_params.get('event_id')
        if event_id:
            queryset = queryset.filter(event_id=event_id)

        return queryset.order_by('execution_order', 'created_at')

    def perform_create(self, serializer):
        """Check permission to use plugins before attaching to event"""
        from rest_framework.exceptions import PermissionDenied

        tenant = getattr(self.request, 'tenant', None)
        if tenant and not tenant.has_feature('can_use_automations'):
            raise PermissionDenied(
                "Your current plan does not include Plugin access. "
                "Please upgrade your subscription to use plugins."
            )

        serializer.save()

    def list(self, request):
        """
        List event plugins.

        Query params:
        - event_id: Filter by event (required for listing)
        """
        event_id = request.query_params.get('event_id')
        if not event_id:
            return Response({
                'error': 'event_id query parameter is required'
            }, status=status.HTTP_400_BAD_REQUEST)

        queryset = self.get_queryset()
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def toggle(self, request, pk=None):
        """Toggle is_active status of an event plugin"""
        event_plugin = self.get_object()
        event_plugin.is_active = not event_plugin.is_active
        event_plugin.save(update_fields=['is_active'])

        serializer = self.get_serializer(event_plugin)
        return Response(serializer.data)

    @action(detail=False, methods=['get'])
    def triggers(self, request):
        """
        Get available trigger options for the UI.

        Returns trigger choices with human-readable labels and
        common offset presets.
        """
        return Response({
            'triggers': [
                {'value': choice[0], 'label': choice[1]}
                for choice in EventPlugin.Trigger.choices
            ],
            'offset_presets': [
                {'value': 0, 'label': 'Immediately'},
                {'value': 5, 'label': '5 minutes'},
                {'value': 10, 'label': '10 minutes'},
                {'value': 15, 'label': '15 minutes'},
                {'value': 30, 'label': '30 minutes'},
                {'value': 60, 'label': '1 hour'},
                {'value': 120, 'label': '2 hours'},
                {'value': 1440, 'label': '1 day'},
            ],
            'timing_groups': [
                {
                    'label': 'Before Event',
                    'triggers': ['before_start'],
                    'supports_offset': True,
                },
                {
                    'label': 'During Event',
                    'triggers': ['at_start', 'after_start'],
                    'supports_offset': True,
                },
                {
                    'label': 'After Event',
                    'triggers': ['after_end'],
                    'supports_offset': True,
                },
                {
                    'label': 'Status Changes',
                    'triggers': ['on_complete', 'on_cancel'],
                    'supports_offset': False,
                },
            ]
        })


class GlobalEventPluginViewSet(viewsets.ModelViewSet):
    """
    API endpoint for managing global event plugin rules.

    Global event plugins automatically attach to ALL events - both existing
    events and new events as they are created.

    Use this for automation rules that should apply across the board, such as:
    - Sending confirmation emails for all appointments
    - Logging all event completions
    - Running cleanup after every event

    Endpoints:
    - GET /api/global-event-plugins/ - List all global rules
    - POST /api/global-event-plugins/ - Create rule (auto-applies to existing events)
    - GET /api/global-event-plugins/{id}/ - Get rule details
    - PATCH /api/global-event-plugins/{id}/ - Update rule
    - DELETE /api/global-event-plugins/{id}/ - Delete rule
    - POST /api/global-event-plugins/{id}/toggle/ - Enable/disable rule
    - POST /api/global-event-plugins/{id}/reapply/ - Reapply to all events
    """
    queryset = GlobalEventPlugin.objects.select_related(
        'plugin_installation',
        'plugin_installation__template',
        'created_by'
    ).all()
    serializer_class = GlobalEventPluginSerializer
    permission_classes = [AllowAny]  # TODO: Change to IsAuthenticated

    def get_queryset(self):
        """Optionally filter by active status"""
        queryset = super().get_queryset()

        is_active = self.request.query_params.get('is_active')
        if is_active is not None:
            queryset = queryset.filter(is_active=is_active.lower() == 'true')

        return queryset.order_by('execution_order', 'created_at')

    def perform_create(self, serializer):
        """Check permission to use plugins and set created_by on creation"""
        from rest_framework.exceptions import PermissionDenied

        tenant = getattr(self.request, 'tenant', None)
        if tenant and not tenant.has_feature('can_use_automations'):
            raise PermissionDenied(
                "Your current plan does not include Plugin access. "
                "Please upgrade your subscription to use plugins."
            )

        user = self.request.user if self.request.user.is_authenticated else None
        serializer.save(created_by=user)

    @action(detail=True, methods=['post'])
    def toggle(self, request, pk=None):
        """Toggle is_active status of a global event plugin rule"""
        global_plugin = self.get_object()
        global_plugin.is_active = not global_plugin.is_active
        global_plugin.save(update_fields=['is_active', 'updated_at'])

        serializer = self.get_serializer(global_plugin)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def reapply(self, request, pk=None):
        """
        Reapply this global rule to all events.

        Useful if:
        - Events were created while the rule was inactive
        - Plugin attachments were manually removed
        """
        global_plugin = self.get_object()

        if not global_plugin.is_active:
            return Response({
                'error': 'Cannot reapply inactive rule. Enable it first.'
            }, status=status.HTTP_400_BAD_REQUEST)

        count = global_plugin.apply_to_all_events()

        return Response({
            'message': f'Applied to {count} events',
            'events_affected': count
        })

    @action(detail=False, methods=['get'])
    def triggers(self, request):
        """
        Get available trigger options for the UI.

        Returns trigger choices with human-readable labels and
        common offset presets (same as EventPlugin).
        """
        return Response({
            'triggers': [
                {'value': choice[0], 'label': choice[1]}
                for choice in EventPlugin.Trigger.choices
            ],
            'offset_presets': [
                {'value': 0, 'label': 'Immediately'},
                {'value': 5, 'label': '5 minutes'},
                {'value': 10, 'label': '10 minutes'},
                {'value': 15, 'label': '15 minutes'},
                {'value': 30, 'label': '30 minutes'},
                {'value': 60, 'label': '1 hour'},
            ],
        })


# =============================================================================
# Time Blocking System ViewSets
# =============================================================================

class HolidayViewSet(viewsets.ReadOnlyModelViewSet):
    """
    API endpoint for viewing holidays.

    Holidays are reference data seeded by the system and cannot be modified
    through the API. Use the `seed_holidays` management command to populate.

    Endpoints:
    - GET /api/holidays/ - List all holidays
    - GET /api/holidays/{code}/ - Get holiday details
    - GET /api/holidays/dates/ - Get holiday dates for a year
    """
    queryset = Holiday.objects.filter(is_active=True)
    serializer_class = HolidaySerializer
    permission_classes = [IsAuthenticated]
    lookup_field = 'code'

    def get_queryset(self):
        """Filter by country if specified"""
        queryset = super().get_queryset()

        country = self.request.query_params.get('country')
        if country:
            queryset = queryset.filter(country=country.upper())

        return queryset.order_by('country', 'name')

    def get_serializer_class(self):
        if self.action == 'list':
            return HolidayListSerializer
        return HolidaySerializer

    @action(detail=False, methods=['get'])
    def dates(self, request):
        """
        Get all holiday dates for a specific year.

        Query params:
        - year: The year to get dates for (default: current year)
        - country: Filter by country code (default: all)

        Response:
        {
            "year": 2025,
            "holidays": [
                {"code": "new_years_day", "name": "New Year's Day", "date": "2025-01-01"},
                ...
            ]
        }
        """
        from datetime import date

        year = request.query_params.get('year')
        if year:
            try:
                year = int(year)
            except ValueError:
                return Response(
                    {'error': 'Invalid year'},
                    status=status.HTTP_400_BAD_REQUEST
                )
        else:
            year = date.today().year

        queryset = self.get_queryset()
        holidays = []

        for holiday in queryset:
            holiday_date = holiday.get_date_for_year(year)
            if holiday_date:
                holidays.append({
                    'code': holiday.code,
                    'name': holiday.name,
                    'date': holiday_date.isoformat(),
                })

        # Sort by date
        holidays.sort(key=lambda h: h['date'])

        return Response({
            'year': year,
            'holidays': holidays
        })


class TimeBlockViewSet(viewsets.ModelViewSet):
    """
    API endpoint for managing time blocks.

    Time blocks allow businesses to block off time for various purposes:
    - Business closures (holidays, vacations)
    - Resource-specific unavailability (maintenance, personal time)
    - Recurring patterns (weekly lunch breaks, monthly meetings)

    Block Types:
    - HARD: Prevents booking entirely
    - SOFT: Shows warning but allows override

    Block Levels:
    - Business-level: resource=null, applies to all resources
    - Resource-level: resource=ID, applies only to that resource

    Endpoints:
    - GET /api/time-blocks/ - List blocks (filterable)
    - POST /api/time-blocks/ - Create block
    - GET /api/time-blocks/{id}/ - Get block details
    - PATCH /api/time-blocks/{id}/ - Update block
    - DELETE /api/time-blocks/{id}/ - Delete block
    - GET /api/time-blocks/blocked-dates/ - Get expanded dates for calendar
    - POST /api/time-blocks/check-conflicts/ - Preview conflicts before creating
    - GET /api/time-blocks/my-blocks/ - Staff view of their own blocks
    """
    queryset = TimeBlock.objects.select_related('resource', 'created_by').all()
    serializer_class = TimeBlockSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        """Filter time blocks based on query parameters"""
        queryset = super().get_queryset()
        user = self.request.user

        # Filter by level (business or resource)
        level = self.request.query_params.get('level')
        if level == 'business':
            queryset = queryset.filter(resource__isnull=True)
        elif level == 'resource':
            queryset = queryset.filter(resource__isnull=False)

        # Filter by specific resource
        resource_id = self.request.query_params.get('resource_id')
        if resource_id:
            queryset = queryset.filter(resource_id=resource_id)

        # Filter by block type
        block_type = self.request.query_params.get('block_type')
        if block_type:
            queryset = queryset.filter(block_type=block_type.upper())

        # Filter by recurrence type
        recurrence_type = self.request.query_params.get('recurrence_type')
        if recurrence_type:
            queryset = queryset.filter(recurrence_type=recurrence_type.upper())

        # Filter by active status
        is_active = self.request.query_params.get('is_active')
        if is_active is not None:
            queryset = queryset.filter(is_active=is_active.lower() == 'true')

        # Staff can only see their own resource's blocks + business blocks
        if user.role == 'TENANT_STAFF':
            from django.db.models import Q
            staff_resource_ids = list(user.staff_resources.values_list('pk', flat=True))
            if staff_resource_ids:
                queryset = queryset.filter(
                    Q(resource__isnull=True) |  # Business blocks
                    Q(resource_id__in=staff_resource_ids)  # Their resource blocks
                )
            else:
                # Staff without linked resource only see business blocks
                queryset = queryset.filter(resource__isnull=True)

        return queryset.order_by('-created_at')

    def get_serializer_class(self):
        if self.action == 'list':
            return TimeBlockListSerializer
        return TimeBlockSerializer

    @action(detail=False, methods=['get'])
    def blocked_dates(self, request):
        """
        Get expanded blocked dates for calendar visualization.

        Query params:
        - start_date: Start of range (required, YYYY-MM-DD)
        - end_date: End of range (required, YYYY-MM-DD)
        - resource_id: Filter to specific resource (optional)
        - include_business: Include business-level blocks (default: true)

        Response:
        {
            "blocked_dates": [
                {
                    "date": "2025-01-01",
                    "block_type": "HARD",
                    "title": "New Year's Day",
                    "resource_id": null,
                    "all_day": true,
                    "start_time": null,
                    "end_time": null,
                    "time_block_id": 123
                },
                ...
            ]
        }
        """
        from datetime import datetime

        start_date_str = request.query_params.get('start_date')
        end_date_str = request.query_params.get('end_date')

        if not start_date_str or not end_date_str:
            return Response(
                {'error': 'start_date and end_date are required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            start_date = datetime.strptime(start_date_str, '%Y-%m-%d').date()
            end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date()
        except ValueError:
            return Response(
                {'error': 'Invalid date format. Use YYYY-MM-DD'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Get active blocks
        queryset = self.get_queryset().filter(is_active=True)

        resource_id = request.query_params.get('resource_id')
        include_business = request.query_params.get('include_business', 'true').lower() == 'true'

        from django.db.models import Q
        if resource_id:
            if include_business:
                queryset = queryset.filter(
                    Q(resource__isnull=True) | Q(resource_id=resource_id)
                )
            else:
                queryset = queryset.filter(resource_id=resource_id)
        elif not include_business:
            queryset = queryset.filter(resource__isnull=False)

        blocked_dates = []
        business_hours_blocks = []

        for block in queryset:
            # Handle BUSINESS_HOURS blocks specially - they define OPEN time, not blocked time
            if block.purpose == TimeBlock.Purpose.BUSINESS_HOURS:
                business_hours_blocks.append(block)
                continue

            # Regular blocks - add as blocked periods
            dates = block.get_blocked_dates_in_range(start_date, end_date)
            for blocked_info in dates:
                blocked_dates.append({
                    'date': blocked_info['date'].isoformat(),
                    'block_type': block.block_type,
                    'purpose': block.purpose,
                    'title': block.title,
                    'resource_id': block.resource_id,
                    'all_day': block.all_day,
                    'start_time': block.start_time.isoformat() if block.start_time else None,
                    'end_time': block.end_time.isoformat() if block.end_time else None,
                    'time_block_id': block.id,
                })

        # For BUSINESS_HOURS blocks, handle two scenarios:
        # 1. Blocks that already represent CLOSED periods (start_time=00:00 or end_time=23:59)
        #    - These are pre-inverted by the frontend, add them directly
        # 2. Blocks that represent OPEN periods (e.g., 09:00 to 17:00)
        #    - These need to be inverted to show closed periods
        from datetime import time as dt_time, timedelta
        if business_hours_blocks:
            # Separate pre-inverted blocks from open-time blocks
            pre_inverted_blocks = []
            open_time_blocks = []

            for bh_block in business_hours_blocks:
                if bh_block.start_time and bh_block.end_time:
                    # Check if this is a pre-inverted block (already represents closed time)
                    is_before_hours = bh_block.start_time == dt_time(0, 0)
                    is_after_hours = bh_block.end_time >= dt_time(23, 59) or bh_block.end_time == dt_time(0, 0)
                    if is_before_hours or is_after_hours:
                        pre_inverted_blocks.append(bh_block)
                    else:
                        open_time_blocks.append(bh_block)
                elif bh_block.all_day:
                    # All-day blocks go to open_time_blocks for closed-day handling
                    open_time_blocks.append(bh_block)

            # Process pre-inverted blocks - add them directly as blocked periods
            current_date = start_date
            while current_date <= end_date:
                for bh_block in pre_inverted_blocks:
                    if bh_block.blocks_date(current_date):
                        blocked_dates.append({
                            'date': current_date.isoformat(),
                            'block_type': bh_block.block_type,
                            'purpose': 'OUTSIDE_BUSINESS_HOURS',
                            'title': bh_block.title,
                            'resource_id': None,
                            'all_day': False,
                            'start_time': bh_block.start_time.isoformat() if bh_block.start_time else None,
                            'end_time': bh_block.end_time.isoformat() if bh_block.end_time else None,
                            'time_block_id': bh_block.id,
                        })
                current_date += timedelta(days=1)

            # Process open-time blocks - invert to show blocked periods
            if open_time_blocks:
                current_date = start_date
                while current_date <= end_date:
                    # Find business hours for this date
                    day_business_hours = None
                    for bh_block in open_time_blocks:
                        if bh_block.blocks_date(current_date):
                            if not bh_block.all_day and bh_block.start_time and bh_block.end_time:
                                day_business_hours = (bh_block.start_time, bh_block.end_time, bh_block)
                                break

                    if day_business_hours:
                        bh_start, bh_end, bh_block = day_business_hours
                        # Add "before business hours" blocked period (midnight to business start)
                        if bh_start > dt_time(0, 0):
                            blocked_dates.append({
                                'date': current_date.isoformat(),
                                'block_type': 'HARD',
                                'purpose': 'OUTSIDE_BUSINESS_HOURS',
                                'title': 'Before Business Hours',
                                'resource_id': None,
                                'all_day': False,
                                'start_time': '00:00:00',
                                'end_time': bh_start.isoformat(),
                                'time_block_id': bh_block.id,
                            })
                        # Add "after business hours" blocked period (business end to midnight)
                        if bh_end < dt_time(23, 59):
                            blocked_dates.append({
                                'date': current_date.isoformat(),
                                'block_type': 'HARD',
                                'purpose': 'OUTSIDE_BUSINESS_HOURS',
                                'title': 'After Business Hours',
                                'resource_id': None,
                                'all_day': False,
                                'start_time': bh_end.isoformat(),
                                'end_time': '23:59:59',
                                'time_block_id': bh_block.id,
                            })
                    else:
                        # No business hours for this day from open_time_blocks
                        # Check if business is closed (only if we have open_time_blocks defining hours)
                        blocked_dates.append({
                            'date': current_date.isoformat(),
                            'block_type': 'HARD',
                            'purpose': 'BUSINESS_CLOSED',
                            'title': 'Business Closed',
                            'resource_id': None,
                            'all_day': True,
                            'start_time': None,
                            'end_time': None,
                            'time_block_id': open_time_blocks[0].id,
                        })

                    current_date += timedelta(days=1)
            elif pre_inverted_blocks:
                # Only pre-inverted blocks exist - check for days with no blocks (business closed)
                current_date = start_date
                while current_date <= end_date:
                    has_blocks_for_day = any(
                        bh_block.blocks_date(current_date) for bh_block in pre_inverted_blocks
                    )
                    if not has_blocks_for_day:
                        blocked_dates.append({
                            'date': current_date.isoformat(),
                            'block_type': 'HARD',
                            'purpose': 'BUSINESS_CLOSED',
                            'title': 'Business Closed',
                            'resource_id': None,
                            'all_day': True,
                            'start_time': None,
                            'end_time': None,
                            'time_block_id': pre_inverted_blocks[0].id,
                        })
                    current_date += timedelta(days=1)

        # Sort by date
        blocked_dates.sort(key=lambda d: (d['date'], d['resource_id'] or 0))

        return Response({
            'blocked_dates': blocked_dates,
            'start_date': start_date_str,
            'end_date': end_date_str,
        })

    @action(detail=False, methods=['post'])
    def check_conflicts(self, request):
        """
        Check for conflicts before creating a time block.

        Request body:
        {
            "recurrence_type": "NONE",
            "recurrence_pattern": {},
            "start_date": "2025-01-15",
            "end_date": "2025-01-15",
            "resource_id": null,
            "all_day": true
        }

        Response:
        {
            "has_conflicts": true,
            "conflict_count": 3,
            "conflicts": [
                {
                    "event_id": 123,
                    "title": "Appointment - John Doe",
                    "start_time": "2025-01-15T10:00:00",
                    "end_time": "2025-01-15T11:00:00"
                },
                ...
            ]
        }
        """
        serializer = CheckConflictsSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        from datetime import date, timedelta

        # Create a temporary TimeBlock to use its date calculation methods
        temp_block = TimeBlock(
            recurrence_type=data['recurrence_type'],
            recurrence_pattern=data.get('recurrence_pattern', {}),
            start_date=data.get('start_date'),
            end_date=data.get('end_date'),
            all_day=data.get('all_day', True),
            start_time=data.get('start_time'),
            end_time=data.get('end_time'),
        )

        # Get blocked dates for next 90 days
        today = date.today()
        check_end = today + timedelta(days=90)
        blocked_dates = temp_block.get_blocked_dates_in_range(today, check_end)

        if not blocked_dates:
            return Response({
                'has_conflicts': False,
                'conflict_count': 0,
                'conflicts': []
            })

        # Find conflicting events
        resource_id = data.get('resource_id')
        conflicts = []

        for blocked_date in blocked_dates:
            events = Event.objects.filter(
                start_time__date=blocked_date,
                status__in=['SCHEDULED', 'CONFIRMED']
            )

            if resource_id:
                from django.contrib.contenttypes.models import ContentType
                resource_ct = ContentType.objects.get_for_model(Resource)
                events = events.filter(
                    participants__content_type=resource_ct,
                    participants__object_id=resource_id
                )

            for event in events[:10]:  # Limit to 10 per date
                conflicts.append({
                    'event_id': event.id,
                    'title': event.title,
                    'start_time': event.start_time.isoformat(),
                    'end_time': event.end_time.isoformat(),
                })

        return Response({
            'has_conflicts': len(conflicts) > 0,
            'conflict_count': len(conflicts),
            'conflicts': conflicts[:20],  # Limit total to 20
        })

    @action(detail=False, methods=['get'])
    def my_blocks(self, request):
        """
        Get time blocks for the current staff member's resource.

        This endpoint is for staff members to view their own availability.
        Returns both their resource blocks and business-level blocks.

        Response includes blocks organized by type for easy display.
        """
        from django_tenants.utils import schema_context

        user = request.user

        # Staff must have a tenant
        if not user.tenant:
            return Response({
                'business_blocks': [],
                'my_blocks': [],
                'resource_id': None,
                'resource_name': None,
                'can_self_approve': False,
                'message': 'No tenant associated with user'
            })

        # Use the user's tenant schema context to find their linked resource
        # This is necessary because the user may access from a different subdomain
        # (e.g., platform.lvh.me) than their actual tenant
        with schema_context(user.tenant.schema_name):
            # Find the resource linked to this user
            linked_resource = Resource.objects.filter(user=user).first()

            if not linked_resource:
                return Response({
                    'business_blocks': [],
                    'my_blocks': [],
                    'resource_id': None,
                    'resource_name': None,
                    'can_self_approve': user.can_self_approve_time_off(),
                    'message': 'You do not have a linked resource'
                })

            # Get business blocks
            business_blocks = TimeBlock.objects.filter(
                resource__isnull=True,
                is_active=True
            ).order_by('-created_at')

            # Get blocks for user's resource (include all statuses so they can see pending/denied)
            my_blocks_qs = TimeBlock.objects.filter(
                resource=linked_resource
            ).order_by('-created_at')

            return Response({
                'business_blocks': TimeBlockListSerializer(business_blocks, many=True).data,
                'my_blocks': TimeBlockListSerializer(my_blocks_qs, many=True).data,
                'resource_id': linked_resource.id,
                'resource_name': linked_resource.name,
                'can_self_approve': user.can_self_approve_time_off(),
            })

    @action(detail=True, methods=['post'])
    def toggle(self, request, pk=None):
        """Toggle the is_active status of a time block"""
        block = self.get_object()
        block.is_active = not block.is_active
        block.save(update_fields=['is_active', 'updated_at'])

        return Response({
            'id': block.id,
            'is_active': block.is_active,
            'message': f"Block {'activated' if block.is_active else 'deactivated'}"
        })

    def perform_create(self, serializer):
        """
        Create time block with appropriate approval status.

        - Owners/Managers: Blocks are auto-approved
        - Staff with can_self_approve_time_off permission: Auto-approved
        - Staff without permission: Block is PENDING approval
        """
        user = self.request.user

        # Determine approval status based on user permissions
        if user.can_self_approve_time_off():
            approval_status = TimeBlock.ApprovalStatus.APPROVED
        else:
            approval_status = TimeBlock.ApprovalStatus.PENDING

        serializer.save(
            created_by=user,
            approval_status=approval_status
        )

    @action(detail=False, methods=['get'])
    def pending_reviews(self, request):
        """
        Get time blocks pending review.

        Only accessible to owners and managers.
        Returns pending time block requests from staff.
        """
        user = request.user

        # Only owners and managers can review
        if not user.can_review_time_off_requests():
            return Response(
                {'error': 'You do not have permission to review time off requests'},
                status=status.HTTP_403_FORBIDDEN
            )

        pending_blocks = TimeBlock.objects.filter(
            approval_status=TimeBlock.ApprovalStatus.PENDING,
            is_active=True
        ).select_related('resource', 'created_by').order_by('created_at')

        return Response({
            'count': pending_blocks.count(),
            'pending_blocks': TimeBlockListSerializer(pending_blocks, many=True).data
        })

    @action(detail=True, methods=['post'])
    def approve(self, request, pk=None):
        """
        Approve a pending time block.

        Only accessible to owners and managers.
        """
        user = request.user

        if not user.can_review_time_off_requests():
            return Response(
                {'error': 'You do not have permission to approve time off requests'},
                status=status.HTTP_403_FORBIDDEN
            )

        block = self.get_object()

        if block.approval_status != TimeBlock.ApprovalStatus.PENDING:
            return Response(
                {'error': f'This block is already {block.get_approval_status_display()}'},
                status=status.HTTP_400_BAD_REQUEST
            )

        from django.utils import timezone

        block.approval_status = TimeBlock.ApprovalStatus.APPROVED
        block.reviewed_by = user
        block.reviewed_at = timezone.now()
        block.review_notes = request.data.get('notes', '')
        block.save(update_fields=['approval_status', 'reviewed_by', 'reviewed_at', 'review_notes', 'updated_at'])

        return Response({
            'id': block.id,
            'approval_status': block.approval_status,
            'message': 'Time block approved successfully'
        })

    @action(detail=True, methods=['post'])
    def deny(self, request, pk=None):
        """
        Deny a pending time block.

        Only accessible to owners and managers.
        """
        user = request.user

        if not user.can_review_time_off_requests():
            return Response(
                {'error': 'You do not have permission to deny time off requests'},
                status=status.HTTP_403_FORBIDDEN
            )

        block = self.get_object()

        if block.approval_status != TimeBlock.ApprovalStatus.PENDING:
            return Response(
                {'error': f'This block is already {block.get_approval_status_display()}'},
                status=status.HTTP_400_BAD_REQUEST
            )

        from django.utils import timezone

        block.approval_status = TimeBlock.ApprovalStatus.DENIED
        block.reviewed_by = user
        block.reviewed_at = timezone.now()
        block.review_notes = request.data.get('notes', '')
        block.save(update_fields=['approval_status', 'reviewed_by', 'reviewed_at', 'review_notes', 'updated_at'])

        return Response({
            'id': block.id,
            'approval_status': block.approval_status,
            'message': 'Time block denied'
        })


# =============================================================================
# Location Management ViewSet
# =============================================================================

class LocationViewSet(TenantFilteredQuerySetMixin, viewsets.ModelViewSet):
    """
    ViewSet for Location CRUD with:
    - Tenant scoping via business FK
    - Atomic primary management via LocationService
    - Invariant: at least one active location must exist

    Endpoints:
    - GET /api/locations/ - List locations (active only by default)
    - POST /api/locations/ - Create location (first is auto-primary)
    - GET /api/locations/{id}/ - Get location details
    - PATCH /api/locations/{id}/ - Update location
    - DELETE /api/locations/{id}/ - Delete location (with invariant check)
    - POST /api/locations/{id}/set_primary/ - Set as primary location
    - POST /api/locations/{id}/set_active/ - Activate/deactivate location
    """
    queryset = Location.objects.all()
    serializer_class = LocationSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        """
        Scope locations by tenant (business FK).
        Returns empty queryset if no tenant context.
        """
        tenant = getattr(self.request, 'tenant', None)

        if not tenant:
            return Location.objects.none()

        qs = Location.objects.filter(business=tenant)

        # Apply include_inactive filter (default: active only)
        if self.request.query_params.get('include_inactive') != 'true':
            qs = qs.filter(is_active=True)

        return qs.order_by('display_order', 'name')

    def perform_create(self, serializer):
        """
        Set business FK on create.
        First location for a tenant automatically becomes primary.
        """
        tenant = self.request.tenant
        is_first = not Location.objects.filter(business=tenant).exists()
        serializer.save(
            business=tenant,
            is_primary=is_first,
            is_active=True,
        )

    @action(detail=True, methods=['post'])
    def set_primary(self, request, pk=None):
        """
        Set this location as primary (atomic via LocationService).
        POST /api/locations/{id}/set_primary/
        """
        location = self.get_object()
        updated = LocationService.set_primary(location.business, location.pk)
        return Response(LocationSerializer(updated).data)

    @action(detail=True, methods=['post'])
    def set_active(self, request, pk=None):
        """
        Explicitly set location active status.
        POST /api/locations/{id}/set_active/
        Body: {"is_active": true|false}

        Enforces:
        - Cannot deactivate last active location
        - Reactivation respects quota limits
        """
        location = self.get_object()
        tenant = location.business
        new_active = request.data.get('is_active')

        if new_active is None:
            return Response(
                {'detail': 'is_active field is required.'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # No change needed
        if location.is_active == new_active:
            return Response(LocationSerializer(location).data)

        if not new_active:
            # DEACTIVATING - check invariant
            is_valid, error = LocationService.validate_at_least_one_active(
                tenant, excluding_location_id=location.pk
            )
            if not is_valid:
                return Response({'detail': error}, status=status.HTTP_400_BAD_REQUEST)

            location.is_active = False
            location.save(update_fields=['is_active', 'updated_at'])
        else:
            # ACTIVATING - use centralized quota check
            can_add, error = LocationService.can_add_location(tenant)
            if not can_add:
                return Response({'detail': error}, status=status.HTTP_400_BAD_REQUEST)

            location.is_active = True
            location.save(update_fields=['is_active', 'updated_at'])

        return Response(LocationSerializer(location).data)

    def perform_destroy(self, instance):
        """
        Delete location with atomic primary promotion.
        Enforces: cannot delete last active location.
        """
        from rest_framework.exceptions import ValidationError

        tenant = instance.business

        # Check invariant
        is_valid, error = LocationService.validate_at_least_one_active(
            tenant, excluding_location_id=instance.pk
        )
        if not is_valid:
            raise ValidationError({'detail': error})

        # If deleting primary, promote next (atomic via service)
        if instance.is_primary:
            LocationService.promote_next_primary(tenant, instance.pk)

        instance.delete()


# ============================================================================
# Media Gallery ViewSets
# ============================================================================

from .models import Album, MediaFile
from .serializers import (
    AlbumSerializer,
    MediaFileSerializer,
    MediaFileUpdateSerializer,
    StorageUsageSerializer,
)
from rest_framework.views import APIView
from django.db.models import Count


class AlbumViewSet(TenantFilteredQuerySetMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing Albums.

    Endpoints:
    - GET /api/albums/ - List all albums
    - POST /api/albums/ - Create album
    - GET /api/albums/{id}/ - Get album details
    - PATCH /api/albums/{id}/ - Update album
    - DELETE /api/albums/{id}/ - Delete album (files moved to uncategorized)
    """
    queryset = Album.objects.all()
    serializer_class = AlbumSerializer
    permission_classes = [IsAuthenticated]
    ordering = ['-created_at']

    def get_queryset(self):
        """Annotate with file count for list view."""
        queryset = super().get_queryset()
        return queryset.annotate(file_count=Count('files'))

    def perform_destroy(self, instance):
        """When deleting an album, move files to uncategorized (null album)."""
        # Move all files in this album to no album
        instance.files.update(album=None)
        instance.delete()


class MediaFileViewSet(TenantFilteredQuerySetMixin, viewsets.ModelViewSet):
    """
    API endpoint for managing Media Files.

    Endpoints:
    - GET /api/media/ - List all files (filterable by album)
    - POST /api/media/ - Upload file
    - GET /api/media/{id}/ - Get file details
    - PATCH /api/media/{id}/ - Update alt text, album
    - DELETE /api/media/{id}/ - Delete file

    Query parameters:
    - album: Filter by album ID (use 'null' for uncategorized)
    """
    queryset = MediaFile.objects.all()
    serializer_class = MediaFileSerializer
    permission_classes = [IsAuthenticated]
    ordering = ['-created_at']

    def get_serializer_class(self):
        """Use different serializer for updates."""
        if self.action in ['update', 'partial_update']:
            return MediaFileUpdateSerializer
        return MediaFileSerializer

    def get_queryset(self):
        """Filter by album if requested."""
        queryset = super().get_queryset()

        album = self.request.query_params.get('album')
        if album is not None:
            if album.lower() == 'null' or album == '':
                # Get files with no album
                queryset = queryset.filter(album__isnull=True)
            else:
                try:
                    queryset = queryset.filter(album_id=int(album))
                except ValueError:
                    pass

        return queryset.select_related('album')

    def perform_destroy(self, instance):
        """Delete file and update storage usage."""
        from smoothschedule.identity.core.services import StorageQuotaService

        file_size = instance.file_size
        tenant = self.request.tenant

        # Delete the instance (which also deletes the file from storage)
        instance.delete()

        # Update storage usage
        if tenant:
            StorageQuotaService.update_usage(tenant, -file_size, -1)

    @action(detail=False, methods=['post'])
    def bulk_move(self, request):
        """
        Move multiple files to an album.
        POST /api/media/bulk_move/
        Body: {"file_ids": [1, 2, 3], "album_id": 5}  # null to uncategorize
        """
        file_ids = request.data.get('file_ids', [])
        album_id = request.data.get('album_id')

        if not file_ids:
            return Response(
                {'error': 'file_ids is required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Validate album exists if specified
        album = None
        if album_id is not None:
            try:
                album = Album.objects.get(id=album_id)
            except Album.DoesNotExist:
                return Response(
                    {'error': 'Album not found'},
                    status=status.HTTP_404_NOT_FOUND
                )

        # Update files
        updated = MediaFile.objects.filter(id__in=file_ids).update(album=album)

        return Response({'updated': updated})

    @action(detail=False, methods=['post'])
    def bulk_delete(self, request):
        """
        Delete multiple files.
        POST /api/media/bulk_delete/
        Body: {"file_ids": [1, 2, 3]}
        """
        from smoothschedule.identity.core.services import StorageQuotaService

        file_ids = request.data.get('file_ids', [])

        if not file_ids:
            return Response(
                {'error': 'file_ids is required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # Get files and calculate total size
        files = MediaFile.objects.filter(id__in=file_ids)
        total_size = sum(f.file_size for f in files)
        count = files.count()

        # Delete files
        for f in files:
            f.delete()  # This also deletes from storage

        # Update storage usage
        tenant = request.tenant
        if tenant and total_size > 0:
            StorageQuotaService.update_usage(tenant, -total_size, -count)

        return Response({'deleted': count})


class StorageUsageView(APIView):
    """
    API endpoint for getting storage usage.

    GET /api/storage-usage/
    """
    permission_classes = [IsAuthenticated]

    def get(self, request):
        from smoothschedule.identity.core.services import StorageQuotaService

        tenant = request.tenant
        if not tenant:
            return Response(
                {'error': 'Tenant not found'},
                status=status.HTTP_400_BAD_REQUEST
            )

        usage = StorageQuotaService.get_usage(tenant)
        serializer = StorageUsageSerializer(usage)
        return Response(serializer.data)