"""
DRF Serializers for Schedule App with Availability Validation
"""
from rest_framework import serializers
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError as DjangoValidationError
from .models import Resource, Event, Participant, Service, ResourceType, ScheduledTask, TaskExecutionLog, PluginTemplate, PluginInstallation, EventPlugin, GlobalEventPlugin, Holiday, TimeBlock, Location, Album, MediaFile
from .services import AvailabilityService
from smoothschedule.identity.users.models import User, StaffRole
from smoothschedule.identity.core.mixins import TimezoneSerializerMixin


class ResourceTypeSerializer(serializers.ModelSerializer):
    """Serializer for custom resource types"""

    class Meta:
        model = ResourceType
        fields = ['id', 'name', 'description', 'category', 'is_default', 'icon_name', 'created_at', 'updated_at']
        read_only_fields = ['id', 'created_at', 'updated_at', 'is_default']

    def validate(self, attrs):
        # If updating, check if trying to modify is_default
        if self.instance and self.instance.is_default:
            if 'name' in attrs and attrs['name'] != self.instance.name:
                # Allow renaming default types
                pass
        return attrs

    def delete(self, instance):
        """Validate before deletion"""
        if instance.is_default:
            raise serializers.ValidationError("Cannot delete default resource types.")
        if instance.resources.exists():
            raise serializers.ValidationError(
                f"Cannot delete resource type '{instance.name}' because it is in use by {instance.resources.count()} resource(s)."
            )


class StaffRoleSerializer(serializers.ModelSerializer):
    """
    Serializer for StaffRole model.

    Provides CRUD operations for tenant-scoped staff roles with
    permission management.
    """
    staff_count = serializers.IntegerField(read_only=True)
    can_delete = serializers.SerializerMethodField()

    class Meta:
        model = StaffRole
        fields = [
            'id', 'name', 'description', 'permissions', 'is_default',
            'staff_count', 'can_delete', 'created_at', 'updated_at',
        ]
        read_only_fields = ['id', 'is_default', 'staff_count', 'can_delete', 'created_at', 'updated_at']

    def get_can_delete(self, obj):
        """Check if this role can be deleted"""
        return obj.can_delete()

    def validate_name(self, value):
        """Ensure role name is unique within tenant"""
        request = self.context.get('request')
        tenant = getattr(request, 'tenant', None) if request else None

        if not tenant:
            return value

        existing = StaffRole.objects.filter(tenant=tenant, name=value)
        if self.instance:
            existing = existing.exclude(pk=self.instance.pk)
        if existing.exists():
            raise serializers.ValidationError("A role with this name already exists.")
        return value

    def validate_permissions(self, value):
        """Validate that permissions is a dict with boolean values"""
        if not isinstance(value, dict):
            raise serializers.ValidationError("Permissions must be a dictionary.")
        for key, val in value.items():
            if not isinstance(key, str):
                raise serializers.ValidationError("Permission keys must be strings.")
            if not isinstance(val, bool):
                raise serializers.ValidationError(f"Permission '{key}' must be a boolean value.")
        return value


class LocationSerializer(serializers.ModelSerializer):
    """
    Serializer for Location model.

    Includes annotated counts from queryset (resource_count, service_count).
    is_primary is read-only - use set_primary action to change.
    """
    resource_count = serializers.IntegerField(read_only=True, required=False)
    service_count = serializers.IntegerField(read_only=True, required=False)

    class Meta:
        model = Location
        fields = [
            'id', 'name', 'address_line1', 'address_line2', 'city', 'state',
            'postal_code', 'country', 'phone', 'email', 'timezone',
            'is_active', 'is_primary', 'display_order',
            'resource_count', 'service_count', 'created_at', 'updated_at',
        ]
        read_only_fields = ['id', 'created_at', 'updated_at', 'is_primary']


class LocationRequiredMixin:
    """
    Mixin for serializers that need to enforce location requirement.
    Handles create AND update/PATCH correctly.
    Uses centralized LocationService.requires_location_on_entities().

    Subclasses should call self._validate_location_required(attrs) in their validate method.
    """

    def _get_effective_location(self, attrs):
        """Get location from attrs or instance (for partial updates)."""
        if 'location' in attrs:
            return attrs.get('location')
        if self.instance:
            return getattr(self.instance, 'location', None)
        return None

    def _get_tenant(self):
        """Get tenant from request context."""
        request = self.context.get('request')
        return getattr(request, 'tenant', None) if request else None

    def _validate_location_required(self, attrs, error_message=None):
        """
        Validate that location is provided when required.

        Args:
            attrs: The validated data from serializer
            error_message: Custom error message (optional)

        Raises:
            ValidationError if location is required but not provided
        """
        from .services import LocationService

        tenant = self._get_tenant()
        if not LocationService.requires_location_on_entities(tenant):
            return  # No validation needed

        effective_location = self._get_effective_location(attrs)
        if not effective_location:
            msg = error_message or 'Location is required when multiple locations exist.'
            raise serializers.ValidationError({'location': msg})


class CustomerSerializer(serializers.ModelSerializer):
    """Serializer for Customer (User with role=CUSTOMER)"""
    name = serializers.SerializerMethodField()
    first_name = serializers.CharField(write_only=True, required=False, allow_blank=True)
    last_name = serializers.CharField(write_only=True, required=False, allow_blank=True)
    total_spend = serializers.SerializerMethodField()
    last_visit = serializers.SerializerMethodField()
    status = serializers.SerializerMethodField()
    avatar_url = serializers.SerializerMethodField()
    tags = serializers.SerializerMethodField()
    user_id = serializers.IntegerField(source='id', read_only=True)
    city = serializers.SerializerMethodField()
    state = serializers.SerializerMethodField()
    zip = serializers.SerializerMethodField()
    user_data = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = [
            'id', 'name', 'first_name', 'last_name', 'email', 'phone', 'city', 'state', 'zip',
            'total_spend', 'last_visit', 'status', 'avatar_url', 'tags',
            'user_id', 'user_data',
        ]
        read_only_fields = ['id']

    def create(self, validated_data):
        """Create a customer with email as username"""
        import uuid
        email = validated_data.get('email', '')
        # Use email as username, or generate a UUID-based username if no email
        if email:
            validated_data['username'] = email.lower()
        else:
            validated_data['username'] = f"customer_{uuid.uuid4().hex[:8]}"

        return super().create(validated_data)

    def get_name(self, obj):
        return obj.full_name

    def get_total_spend(self, obj):
        # TODO: Calculate from payments when implemented
        return 0

    def get_last_visit(self, obj):
        # TODO: Get from last appointment when implemented
        return None

    def get_status(self, obj):
        return 'Active' if obj.is_active else 'Inactive'

    def get_avatar_url(self, obj):
        return None  # TODO: Implement avatar

    def get_tags(self, obj):
        return []  # TODO: Implement customer tags

    def get_city(self, obj):
        return ''  # TODO: Add address fields to User model

    def get_state(self, obj):
        return ''

    def get_zip(self, obj):
        return ''

    def get_user_data(self, obj):
        """Return user data needed for masquerading"""
        return {
            'id': obj.id,
            'username': obj.username,
            'name': obj.full_name,
            'email': obj.email,
            'role': 'customer',
        }


class StaffSerializer(serializers.ModelSerializer):
    """Serializer for Staff members (Users with staff roles)"""
    name = serializers.SerializerMethodField()
    role = serializers.SerializerMethodField()
    can_invite_staff = serializers.SerializerMethodField()

    # Staff role fields
    staff_role_id = serializers.PrimaryKeyRelatedField(
        source='staff_role',
        queryset=StaffRole.objects.all(),
        required=False,
        allow_null=True,
    )
    staff_role_name = serializers.CharField(source='staff_role.name', read_only=True)
    effective_permissions = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = [
            'id', 'username', 'name', 'email', 'phone', 'role',
            'is_active', 'permissions', 'can_invite_staff',
            'staff_role_id', 'staff_role_name', 'effective_permissions',
        ]
        read_only_fields = ['id', 'username', 'email', 'role', 'can_invite_staff', 'effective_permissions']

    def get_name(self, obj):
        return obj.full_name

    def get_role(self, obj):
        # Map database roles to frontend roles
        role_mapping = {
            'TENANT_OWNER': 'owner',
            'TENANT_MANAGER': 'manager',
            'TENANT_STAFF': 'staff',
        }
        return role_mapping.get(obj.role, obj.role.lower())

    def get_can_invite_staff(self, obj):
        return obj.can_invite_staff()

    def get_effective_permissions(self, obj):
        """Get merged permissions (role + user overrides)"""
        return obj.get_effective_permissions()

    def validate_staff_role_id(self, value):
        """Validate that the staff role belongs to the same tenant"""
        if value is None:
            return value

        request = self.context.get('request')
        tenant = getattr(request, 'tenant', None) if request else None

        if tenant and value.tenant_id != tenant.id:
            raise serializers.ValidationError("Staff role must belong to the same business.")

        return value


class ServiceSerializer(serializers.ModelSerializer):
    """Serializer for Service model"""
    duration_minutes = serializers.IntegerField(source='duration', read_only=True)
    deposit_display = serializers.SerializerMethodField()
    requires_deposit = serializers.BooleanField(read_only=True)
    requires_saved_payment_method = serializers.BooleanField(read_only=True)
    resource_names = serializers.SerializerMethodField()

    # Read as dollars from property, write converts to cents
    price = serializers.DecimalField(max_digits=10, decimal_places=2, required=False)
    deposit_amount = serializers.DecimalField(max_digits=10, decimal_places=2, required=False, allow_null=True)

    class Meta:
        model = Service
        fields = [
            'id', 'name', 'description', 'duration', 'duration_minutes',
            'price', 'price_cents', 'display_order', 'photos', 'is_active', 'created_at', 'updated_at',
            'is_archived_by_quota',
            # Pricing fields
            'variable_pricing', 'deposit_amount', 'deposit_amount_cents', 'deposit_percent',
            'requires_deposit', 'requires_saved_payment_method', 'deposit_display',
            # Resource assignment
            'all_resources', 'resource_ids', 'resource_names',
        ]
        read_only_fields = ['created_at', 'updated_at', 'is_archived_by_quota',
                          'deposit_display', 'requires_deposit', 'requires_saved_payment_method', 'resource_names']

    def to_internal_value(self, data):
        """Convert price/deposit_amount from dollars to cents for writing"""
        internal = super().to_internal_value(data)

        # Convert price to price_cents if price is provided
        if 'price' in data:
            from decimal import Decimal
            price = Decimal(str(data['price']))
            internal['price_cents'] = int(price * 100)
            internal.pop('price', None)

        # Convert deposit_amount to deposit_amount_cents if provided
        if 'deposit_amount' in data and data['deposit_amount'] is not None:
            from decimal import Decimal
            deposit = Decimal(str(data['deposit_amount']))
            internal['deposit_amount_cents'] = int(deposit * 100)
            internal.pop('deposit_amount', None)
        elif 'deposit_amount' in data and data['deposit_amount'] is None:
            internal['deposit_amount_cents'] = None
            internal.pop('deposit_amount', None)

        return internal

    def get_deposit_display(self, obj):
        """Get a human-readable description of the deposit requirement"""
        if obj.deposit_amount and obj.deposit_amount > 0:
            return f"${obj.deposit_amount} deposit"
        if obj.deposit_percent and obj.deposit_percent > 0:
            return f"{obj.deposit_percent}% deposit"
        return None

    def get_resource_names(self, obj):
        """Get names of assigned resources (for display when all_resources=False)"""
        if obj.all_resources or not obj.resource_ids:
            return []
        # Fetch resource names for the IDs
        resources = Resource.objects.filter(id__in=obj.resource_ids)
        return [r.name for r in resources]

    def validate(self, attrs):
        """Validate deposit configuration"""
        variable_pricing = attrs.get('variable_pricing', getattr(self.instance, 'variable_pricing', False))
        deposit_percent = attrs.get('deposit_percent', getattr(self.instance, 'deposit_percent', None))

        # Variable pricing cannot use percentage deposit
        if variable_pricing and deposit_percent and deposit_percent > 0:
            raise serializers.ValidationError({
                'deposit_percent': 'Variable pricing services cannot use percentage deposits. Use a fixed amount instead.'
            })

        # Validate deposit_percent range
        if deposit_percent and deposit_percent > 100:
            raise serializers.ValidationError({
                'deposit_percent': 'Deposit percentage cannot exceed 100%'
            })

        return attrs


class ResourceSerializer(LocationRequiredMixin, serializers.ModelSerializer):
    """Serializer for Resource model"""
    capacity_description = serializers.SerializerMethodField()
    user_id = serializers.IntegerField(required=False, allow_null=True)
    user_name = serializers.CharField(source='user.full_name', read_only=True, allow_null=True)

    class Meta:
        model = Resource
        fields = [
            'id', 'name', 'type', 'user_id', 'user_name',
            'description', 'max_concurrent_events',
            'buffer_duration', 'is_active', 'capacity_description',
            'saved_lane_count', 'created_at', 'updated_at',
            'is_archived_by_quota', 'user_can_edit_schedule',
            'location', 'is_mobile',
        ]
        read_only_fields = ['created_at', 'updated_at', 'is_archived_by_quota']

    def get_capacity_description(self, obj):
        if obj.max_concurrent_events == 0:
            return "Unlimited capacity"
        elif obj.max_concurrent_events == 1:
            return "Exclusive use (1 at a time)"
        return f"Up to {obj.max_concurrent_events} concurrent events"

    def to_representation(self, instance):
        """Add user_id to the output"""
        ret = super().to_representation(instance)
        ret['user_id'] = instance.user_id
        return ret

    def _get_valid_user(self, user_id):
        """
        Get a user by ID, validating they belong to the same tenant as the request.
        Returns None if user doesn't exist or doesn't belong to the same tenant.

        CRITICAL: This prevents cross-tenant user linking (multi-tenancy security).

        Uses request.tenant (from django-tenants middleware) rather than request.user.tenant
        because platform-level users (owners) may have tenant=None on their user record
        but still access tenant subdomains.
        """
        import logging
        logger = logging.getLogger(__name__)

        logger.warning(f"_get_valid_user called with user_id={user_id}")

        if not user_id:
            logger.warning("_get_valid_user: user_id is empty, returning None")
            return None

        request = self.context.get('request')
        if not request or not request.user.is_authenticated:
            logger.warning(f"_get_valid_user: no request or not authenticated, returning None")
            return None

        # Use request.tenant (from django-tenants middleware) - this is set based on
        # the subdomain being accessed, not the user's tenant FK
        tenant = getattr(request, 'tenant', None)
        logger.warning(f"_get_valid_user: request.tenant={tenant}, request.user={request.user}, request.user.tenant={getattr(request.user, 'tenant', 'N/A')}")

        if not tenant:
            logger.warning("_get_valid_user: no tenant on request, returning None")
            return None

        try:
            user = User.objects.get(id=user_id)
            logger.warning(f"_get_valid_user: found user {user.email}, user.tenant={user.tenant}, comparing to request.tenant={tenant}")

            # Verify user belongs to the same tenant as the request
            if user.tenant == tenant:
                logger.warning(f"_get_valid_user: tenant match! Returning user")
                return user

            logger.warning(f"_get_valid_user: tenant mismatch, returning None")
            return None
        except User.DoesNotExist:
            logger.warning(f"_get_valid_user: user {user_id} not found")
            return None

    def _is_staff_type(self, resource_type=None, legacy_type=None):
        """
        Check if a resource is a staff type (requires user assignment).
        Checks both the new resource_type and legacy type field.
        """
        # Check new resource_type FK
        if resource_type:
            return resource_type.category == ResourceType.Category.STAFF

        # Fall back to legacy type field
        if legacy_type:
            return legacy_type == Resource.Type.STAFF

        return False

    def validate(self, attrs):
        """
        Validate that staff-type resources have a user assigned.
        Staff resources MUST be linked to a staff member.
        Non-mobile resources require location when multi-location enabled.
        """
        import logging
        logger = logging.getLogger(__name__)
        logger.warning(f"ResourceSerializer.validate called with attrs={attrs}")

        user_id = attrs.get('user_id')
        resource_type = attrs.get('resource_type')
        legacy_type = attrs.get('type')

        # For updates, use existing values if not provided
        if self.instance:
            if resource_type is None:
                resource_type = self.instance.resource_type
            if legacy_type is None:
                legacy_type = self.instance.type
            # Check if user_id is being explicitly cleared or wasn't provided
            if 'user_id' not in attrs:
                user_id = self.instance.user_id

        # Check if this is a staff type
        is_staff_type = self._is_staff_type(resource_type, legacy_type)

        if is_staff_type and not user_id:
            raise serializers.ValidationError({
                'user_id': 'Staff type resources must be assigned to a staff member. '
                          'Please select a staff member or change the resource type.'
            })

        # Location validation: only for non-mobile resources
        is_mobile = attrs.get('is_mobile')
        if is_mobile is None and self.instance:
            is_mobile = self.instance.is_mobile

        if not is_mobile:
            self._validate_location_required(
                attrs,
                error_message='Location is required for non-mobile resources when multiple locations exist.'
            )

        return attrs

    def create(self, validated_data):
        """Handle user_id when creating a resource"""
        user_id = validated_data.pop('user_id', None)
        if user_id:
            user = self._get_valid_user(user_id)
            if user:
                validated_data['user'] = user
        return super().create(validated_data)

    def update(self, instance, validated_data):
        """Handle user_id when updating a resource"""
        user_id = validated_data.pop('user_id', None)
        if user_id is not None:
            if user_id:
                user = self._get_valid_user(user_id)
                if user:
                    validated_data['user'] = user
            else:
                validated_data['user'] = None
        return super().update(instance, validated_data)


class ParticipantSerializer(serializers.ModelSerializer):
    """Serializer for Participant model"""
    content_type_str = serializers.SerializerMethodField()
    participant_display = serializers.SerializerMethodField()
    
    class Meta:
        model = Participant
        fields = [
            'id', 'event', 'role', 'content_type', 'object_id',
            'content_type_str', 'participant_display', 'created_at',
        ]
        read_only_fields = ['created_at']
    
    def get_content_type_str(self, obj):
        return str(obj.content_type)
    
    def get_participant_display(self, obj):
        return str(obj.content_object) if obj.content_object else None


class EventSerializer(LocationRequiredMixin, TimezoneSerializerMixin, serializers.ModelSerializer):
    """
    Serializer for Event model with availability validation.

    CRITICAL: Validates resource availability before saving via AvailabilityService.

    Includes timezone context via TimezoneSerializerMixin:
    - timezone_display_mode: "business" or "viewer"
    - business_timezone: IANA timezone string

    Status mapping (frontend -> backend):
    - PENDING -> SCHEDULED
    - CONFIRMED -> SCHEDULED
    - CANCELLED -> CANCELED
    - NO_SHOW -> NOSHOW
    """
    # Status mapping: frontend value -> backend value
    STATUS_MAPPING = {
        'PENDING': 'SCHEDULED',
        'CONFIRMED': 'SCHEDULED',
        'CANCELLED': 'CANCELED',
        'NO_SHOW': 'NOSHOW',
    }

    # Reverse mapping for serialization: backend value -> frontend value
    STATUS_REVERSE_MAPPING = {
        'SCHEDULED': 'CONFIRMED',
        'CANCELED': 'CANCELLED',
        'NOSHOW': 'NO_SHOW',
    }

    participants = ParticipantSerializer(many=True, read_only=True)
    duration_minutes = serializers.SerializerMethodField()

    # Override status field to allow frontend values
    status = serializers.CharField(required=False)

    # Simplified fields for frontend compatibility
    resource_id = serializers.SerializerMethodField()
    customer_id = serializers.SerializerMethodField()
    service_id = serializers.SerializerMethodField()
    customer_name = serializers.SerializerMethodField()
    service_name = serializers.SerializerMethodField()
    is_paid = serializers.SerializerMethodField()

    # Explicitly declare mixin field for DRF's metaclass
    business_timezone = serializers.SerializerMethodField()

    # Variable pricing fields
    is_variable_pricing = serializers.SerializerMethodField()
    remaining_balance = serializers.SerializerMethodField()
    overpaid_amount = serializers.SerializerMethodField()

    # Write-only fields for creating participants
    resource_ids = serializers.ListField(
        child=serializers.IntegerField(),
        write_only=True,
        required=False,
        help_text="List of Resource IDs to assign"
    )
    staff_ids = serializers.ListField(
        child=serializers.IntegerField(),
        write_only=True,
        required=False,
        help_text="List of Staff (User) IDs to assign"
    )
    customer = serializers.IntegerField(
        write_only=True,
        required=False,
        help_text="Customer (User) ID to assign"
    )
    
    # Write-only field for setting service
    service = serializers.PrimaryKeyRelatedField(
        queryset=Service.objects.all(),
        required=False,
        allow_null=True,
        write_only=True
    )

    class Meta:
        model = Event
        fields = [
            'id', 'title', 'start_time', 'end_time', 'status', 'notes',
            'duration_minutes', 'participants', 'resource_ids', 'staff_ids', 'customer',
            'resource_id', 'customer_id', 'service_id', 'customer_name', 'service_name', 'is_paid',
            # Location field
            'location',
            # Pricing fields
            'service', 'deposit_amount', 'deposit_transaction_id', 'final_price',
            'final_charge_transaction_id', 'is_variable_pricing', 'remaining_balance', 'overpaid_amount',
            'created_at', 'updated_at', 'created_by',
            # Timezone context (from TimezoneSerializerMixin)
            'business_timezone',
        ]
        read_only_fields = ['created_at', 'updated_at', 'created_by', 'deposit_transaction_id',
                          'final_charge_transaction_id', 'is_variable_pricing', 'remaining_balance', 'overpaid_amount',
                          'business_timezone']

    def get_duration_minutes(self, obj):
        return int(obj.duration.total_seconds() / 60)

    def get_resource_id(self, obj):
        """Get first resource ID from participants"""
        resource_participant = obj.participants.filter(role='RESOURCE').first()
        return resource_participant.object_id if resource_participant else None

    def get_customer_id(self, obj):
        """Get customer ID from participants"""
        customer_participant = obj.participants.filter(role='CUSTOMER').first()
        return customer_participant.object_id if customer_participant else None

    def get_service_id(self, obj):
        """Get service ID from the event's service FK"""
        return obj.service_id if obj.service_id else None

    def get_customer_name(self, obj):
        """Get customer name from participant"""
        customer_participant = obj.participants.filter(role='CUSTOMER').first()
        if customer_participant and customer_participant.content_object:
            user = customer_participant.content_object
            return user.full_name if hasattr(user, 'full_name') else str(user)
        # Fallback to title
        return obj.title.split(' - ')[0] if ' - ' in obj.title else obj.title

    def get_service_name(self, obj):
        """Get service name from the service FK or fallback to title parsing"""
        if obj.service:
            return obj.service.name
        # Fallback: Extract from title format "Customer Name - Service Name"
        if ' - ' in obj.title:
            return obj.title.split(' - ')[-1]
        return "Service"

    def get_is_paid(self, obj):
        """Check if event is paid"""
        return obj.status == 'PAID'

    def get_is_variable_pricing(self, obj):
        """Check if this event uses variable pricing"""
        return obj.is_variable_pricing

    def get_remaining_balance(self, obj):
        """Get the remaining balance after deposit"""
        balance = obj.remaining_balance
        return str(balance) if balance is not None else None

    def get_overpaid_amount(self, obj):
        """Get overpayment amount if deposit exceeds final price"""
        overpaid = obj.overpaid_amount
        return str(overpaid) if overpaid is not None else None

    def validate_status(self, value):
        """Map frontend status values to backend values"""
        if value in self.STATUS_MAPPING:
            return self.STATUS_MAPPING[value]
        # Accept backend values directly
        valid_backend_statuses = [s.value for s in Event.Status]
        if value in valid_backend_statuses:
            return value
        raise serializers.ValidationError(
            f'Invalid status "{value}". Valid values are: '
            f'{", ".join(list(self.STATUS_MAPPING.keys()) + valid_backend_statuses)}'
        )

    def to_representation(self, instance):
        """Map backend status values to frontend values when serializing"""
        data = super().to_representation(instance)
        if 'status' in data and data['status'] in self.STATUS_REVERSE_MAPPING:
            data['status'] = self.STATUS_REVERSE_MAPPING[data['status']]
        return data

    def validate(self, attrs):
        """
        Validate event timing and resource availability.

        Checks:
        1. Location required when multi-location enabled
        2. end_time > start_time
        3. start_time not in past (for new events)
        4. Resource availability using AvailabilityService
        """
        # Validation 1: Location required when multi-location enabled
        self._validate_location_required(attrs)

        # For partial updates, get existing values from instance if not provided
        start_time = attrs.get('start_time')
        end_time = attrs.get('end_time')
        resource_ids = attrs.get('resource_ids', [])

        # If this is a partial update, fill in missing values from existing instance
        if self.instance:
            if start_time is None:
                start_time = self.instance.start_time
            if end_time is None:
                end_time = self.instance.end_time

        # Skip validation if we still don't have both times (shouldn't happen for valid requests)
        if start_time is None or end_time is None:
            return attrs

        # Validation 1: End must be after start
        if end_time <= start_time:
            raise serializers.ValidationError({
                'end_time': 'End time must be after start time'
            })
        
        # Validation 2: No past events (only for new events)
        if not self.instance:
            from django.utils import timezone
            if start_time < timezone.now():
                raise serializers.ValidationError({
                    'start_time': 'Cannot create events in the past'
                })
        
        # Validation 3: Check resource availability
        # CRITICAL: This enforces concurrency limits
        event_id = self.instance.id if self.instance else None
        availability_errors = []
        soft_block_warnings = []

        for resource_id in resource_ids:
            try:
                resource = Resource.objects.get(id=resource_id, is_active=True)
            except Resource.DoesNotExist:
                availability_errors.append(f"Resource ID {resource_id} not found or inactive")
                continue

            # Call the availability service
            is_available, reason, warnings = AvailabilityService.check_availability(
                resource=resource,
                start_time=start_time,
                end_time=end_time,
                exclude_event_id=event_id
            )

            if not is_available:
                availability_errors.append(f"{resource.name}: {reason}")
            else:
                # Collect soft block warnings (these can be overridden)
                soft_block_warnings.extend(warnings)

        if availability_errors:
            raise serializers.ValidationError({
                'non_field_errors': availability_errors
            })

        # Store soft warnings for the view layer to handle (e.g., show confirmation dialog)
        # The frontend can pass force_override=true to proceed despite soft blocks
        if soft_block_warnings and not self.context.get('force_override', False):
            # Add warnings to context so they can be included in response
            self.context['soft_block_warnings'] = soft_block_warnings
        
        return attrs
    
    def create(self, validated_data):
        """Create event and associated participants"""
        resource_ids = validated_data.pop('resource_ids', [])
        staff_ids = validated_data.pop('staff_ids', [])
        customer_id = validated_data.pop('customer', None)

        # Set created_by from request user (only if authenticated)
        request = self.context.get('request')
        if request and hasattr(request, 'user') and request.user.is_authenticated:
            validated_data['created_by'] = request.user
        else:
            validated_data['created_by'] = None  # TODO: Remove for production

        # Create the event
        event = Event.objects.create(**validated_data)

        # Create Resource participants
        resource_content_type = ContentType.objects.get_for_model(Resource)
        for resource_id in resource_ids:
            Participant.objects.create(
                event=event,
                content_type=resource_content_type,
                object_id=resource_id,
                role=Participant.Role.RESOURCE
            )

        # Create Staff participants
        from smoothschedule.identity.users.models import User
        user_content_type = ContentType.objects.get_for_model(User)
        for staff_id in staff_ids:
            Participant.objects.create(
                event=event,
                content_type=user_content_type,
                object_id=staff_id,
                role=Participant.Role.STAFF
            )

        # Create Customer participant
        if customer_id:
            Participant.objects.create(
                event=event,
                content_type=user_content_type,
                object_id=customer_id,
                role=Participant.Role.CUSTOMER
            )

        return event
    
    def update(self, instance, validated_data):
        """Update event. Participants managed separately."""
        validated_data.pop('resource_ids', None)
        validated_data.pop('staff_ids', None)
        validated_data.pop('customer', None)
        
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        
        instance.save()
        return instance


class ScheduledTaskSerializer(serializers.ModelSerializer):
    """Serializer for ScheduledTask model"""

    created_by_name = serializers.SerializerMethodField()
    plugin_display_name = serializers.SerializerMethodField()

    class Meta:
        model = ScheduledTask
        fields = [
            'id',
            'name',
            'description',
            'plugin_name',
            'plugin_display_name',
            'plugin_config',
            'schedule_type',
            'cron_expression',
            'interval_minutes',
            'run_at',
            'status',
            'last_run_at',
            'last_run_status',
            'last_run_result',
            'next_run_at',
            'created_at',
            'updated_at',
            'created_by',
            'created_by_name',
            'celery_task_id',
        ]
        read_only_fields = [
            'id',
            'created_at',
            'updated_at',
            'last_run_at',
            'last_run_status',
            'last_run_result',
            'next_run_at',
            'created_by',
            'celery_task_id',
        ]

    def get_created_by_name(self, obj):
        """Get name of user who created the task"""
        if obj.created_by:
            return obj.created_by.get_full_name() or obj.created_by.username
        return None

    def get_plugin_display_name(self, obj):
        """Get display name of the plugin"""
        from smoothschedule.scheduling.automations.registry import registry
        plugin_class = registry.get(obj.plugin_name)
        if plugin_class:
            return plugin_class.display_name
        return obj.plugin_name

    def validate(self, attrs):
        """Validate schedule configuration"""
        schedule_type = attrs.get('schedule_type')

        if schedule_type == ScheduledTask.ScheduleType.CRON:
            if not attrs.get('cron_expression'):
                raise serializers.ValidationError({
                    'cron_expression': 'Cron expression is required for CRON schedule type'
                })

        if schedule_type == ScheduledTask.ScheduleType.INTERVAL:
            if not attrs.get('interval_minutes'):
                raise serializers.ValidationError({
                    'interval_minutes': 'Interval minutes is required for INTERVAL schedule type'
                })

        if schedule_type == ScheduledTask.ScheduleType.ONE_TIME:
            if not attrs.get('run_at'):
                raise serializers.ValidationError({
                    'run_at': 'Run at datetime is required for ONE_TIME schedule type'
                })

        return attrs

    def validate_plugin_name(self, value):
        """Validate that the plugin exists"""
        from smoothschedule.scheduling.automations.registry import registry
        if not registry.get(value):
            raise serializers.ValidationError(f"Plugin '{value}' not found")
        return value

    def validate_plugin_config(self, value):
        """Validate plugin configuration against schema"""
        if not isinstance(value, dict):
            raise serializers.ValidationError("Plugin config must be a dictionary")
        return value

    def create(self, validated_data):
        """Create scheduled task and calculate next run time"""
        task = super().create(validated_data)
        task.update_next_run_time()
        return task

    def update(self, instance, validated_data):
        """Update scheduled task and recalculate next run time"""
        task = super().update(instance, validated_data)
        task.update_next_run_time()
        return task


class TaskExecutionLogSerializer(serializers.ModelSerializer):
    """Serializer for TaskExecutionLog model"""

    scheduled_task_name = serializers.CharField(source='scheduled_task.name', read_only=True)
    plugin_name = serializers.CharField(source='scheduled_task.plugin_name', read_only=True)

    class Meta:
        model = TaskExecutionLog
        fields = [
            'id',
            'scheduled_task',
            'scheduled_task_name',
            'plugin_name',
            'started_at',
            'completed_at',
            'status',
            'result',
            'error_message',
            'execution_time_ms',
        ]
        read_only_fields = '__all__'


class PluginInfoSerializer(serializers.Serializer):
    """Serializer for plugin metadata"""

    name = serializers.CharField()
    display_name = serializers.CharField()
    description = serializers.CharField()
    category = serializers.CharField()
    config_schema = serializers.DictField()


class PluginTemplateSerializer(serializers.ModelSerializer):
    """Serializer for PluginTemplate model"""

    author_name = serializers.CharField(read_only=True)
    approved_by_name = serializers.SerializerMethodField()
    can_publish = serializers.SerializerMethodField()
    validation_errors = serializers.SerializerMethodField()

    class Meta:
        model = PluginTemplate
        fields = [
            'id', 'name', 'slug', 'description', 'short_description',
            'plugin_code', 'plugin_code_hash', 'template_variables', 'default_config',
            'visibility', 'category', 'tags',
            'author', 'author_name', 'version', 'license_type', 'logo_url',
            'is_approved', 'approved_by', 'approved_by_name', 'approved_at', 'rejection_reason',
            'install_count', 'rating_average', 'rating_count',
            'created_at', 'updated_at', 'published_at',
            'can_publish', 'validation_errors',
        ]
        read_only_fields = [
            'id', 'slug', 'plugin_code_hash', 'template_variables',
            'author', 'author_name', 'is_approved', 'approved_by', 'approved_by_name',
            'approved_at', 'rejection_reason', 'install_count', 'rating_average',
            'rating_count', 'created_at', 'updated_at', 'published_at',
        ]

    def get_approved_by_name(self, obj):
        """Get name of user who approved the plugin"""
        if obj.approved_by:
            return obj.approved_by.get_full_name() or obj.approved_by.username
        return None

    def get_can_publish(self, obj):
        """Check if plugin can be published to marketplace"""
        return obj.can_be_published()

    def get_validation_errors(self, obj):
        """Get validation errors for publishing"""
        from .safe_scripting import validate_plugin_whitelist
        validation = validate_plugin_whitelist(obj.plugin_code)
        if not validation['valid']:
            return validation['errors']
        return []

    def create(self, validated_data):
        """Set author from request user"""
        request = self.context.get('request')
        if request and hasattr(request, 'user'):
            validated_data['author'] = request.user
        return super().create(validated_data)

    def validate_plugin_code(self, value):
        """Validate plugin code and extract template variables"""
        if not value or not value.strip():
            raise serializers.ValidationError("Plugin code cannot be empty")

        # Extract template variables
        from .template_parser import TemplateVariableParser
        try:
            template_vars = TemplateVariableParser.extract_variables(value)
        except Exception as e:
            raise serializers.ValidationError(f"Failed to parse template variables: {str(e)}")

        return value


class PluginTemplateListSerializer(serializers.ModelSerializer):
    """Lightweight serializer for plugin template listing"""

    author_name = serializers.CharField(read_only=True)

    class Meta:
        model = PluginTemplate
        fields = [
            'id', 'name', 'slug', 'short_description', 'description',
            'visibility', 'category', 'tags',
            'author_name', 'version', 'license_type', 'logo_url', 'is_approved',
            'install_count', 'rating_average', 'rating_count',
            'created_at', 'updated_at', 'published_at',
        ]
        read_only_fields = fields  # All fields are read-only for list view


class PluginInstallationSerializer(serializers.ModelSerializer):
    """Serializer for PluginInstallation model"""

    template_name = serializers.CharField(source='template.name', read_only=True)
    template_slug = serializers.CharField(source='template.slug', read_only=True)
    template_description = serializers.CharField(source='template.description', read_only=True)
    category = serializers.CharField(source='template.category', read_only=True)
    version = serializers.CharField(source='template.version', read_only=True)
    author_name = serializers.CharField(source='template.author_name', read_only=True)
    logo_url = serializers.CharField(source='template.logo_url', read_only=True)
    template_variables = serializers.JSONField(source='template.template_variables', read_only=True)
    scheduled_task_name = serializers.CharField(source='scheduled_task.name', read_only=True)
    installed_by_name = serializers.SerializerMethodField()
    has_update = serializers.SerializerMethodField()

    class Meta:
        model = PluginInstallation
        fields = [
            'id', 'template', 'template_name', 'template_slug', 'template_description',
            'category', 'version', 'author_name', 'logo_url', 'template_variables',
            'scheduled_task', 'scheduled_task_name',
            'installed_by', 'installed_by_name', 'installed_at',
            'config_values', 'template_version_hash',
            'rating', 'review', 'reviewed_at',
            'has_update',
        ]
        read_only_fields = [
            'id', 'installed_by', 'installed_by_name', 'installed_at',
            'template_version_hash', 'reviewed_at',
        ]

    def get_installed_by_name(self, obj):
        """Get name of user who installed the plugin"""
        if obj.installed_by:
            return obj.installed_by.get_full_name() or obj.installed_by.username
        return None

    def get_has_update(self, obj):
        """Check if template has been updated"""
        return obj.has_update_available()

    def create(self, validated_data):
        """
        Create plugin installation.

        Installation makes the plugin available in "My Plugins".
        Scheduling is optional and done separately.
        """
        request = self.context.get('request')
        template = validated_data.get('template')

        # Set installed_by from request user
        if request and hasattr(request, 'user') and request.user.is_authenticated:
            validated_data['installed_by'] = request.user

        # Store template version hash for update detection
        if template:
            import hashlib
            validated_data['template_version_hash'] = hashlib.sha256(
                template.plugin_code.encode('utf-8')
            ).hexdigest()

        # Don't require scheduled_task on creation
        # It can be added later when user schedules the plugin
        validated_data.pop('scheduled_task', None)

        return super().create(validated_data)


class EventPluginSerializer(serializers.ModelSerializer):
    """
    Serializer for EventPlugin - attaching plugins to calendar events.

    Provides a visual-friendly representation of when plugins run:
    - trigger: 'before_start', 'at_start', 'after_start', 'after_end', 'on_complete', 'on_cancel'
    - offset_minutes: 0, 5, 10, 15, 30, 60 (for time-based triggers)
    """

    plugin_name = serializers.CharField(source='plugin_installation.template.name', read_only=True)
    plugin_description = serializers.CharField(source='plugin_installation.template.short_description', read_only=True)
    plugin_category = serializers.CharField(source='plugin_installation.template.category', read_only=True)
    plugin_logo_url = serializers.CharField(source='plugin_installation.template.logo_url', read_only=True)
    trigger_display = serializers.CharField(source='get_trigger_display', read_only=True)
    execution_time = serializers.SerializerMethodField()
    timing_description = serializers.SerializerMethodField()

    class Meta:
        model = EventPlugin
        fields = [
            'id',
            'event',
            'plugin_installation',
            'plugin_name',
            'plugin_description',
            'plugin_category',
            'plugin_logo_url',
            'trigger',
            'trigger_display',
            'offset_minutes',
            'timing_description',
            'execution_time',
            'is_active',
            'execution_order',
            'created_at',
        ]
        read_only_fields = ['id', 'created_at']

    def get_execution_time(self, obj):
        """Get the calculated execution time"""
        exec_time = obj.get_execution_time()
        return exec_time.isoformat() if exec_time else None

    def get_timing_description(self, obj):
        """
        Generate a human-readable description of when the plugin runs.
        Examples: "At start", "10 minutes before start", "30 minutes after end"
        """
        trigger = obj.trigger
        offset = obj.offset_minutes

        if trigger == EventPlugin.Trigger.BEFORE_START:
            if offset == 0:
                return "At start"
            return f"{offset} min before start"
        elif trigger == EventPlugin.Trigger.AT_START:
            if offset == 0:
                return "At start"
            return f"{offset} min after start"
        elif trigger == EventPlugin.Trigger.AFTER_START:
            if offset == 0:
                return "At start"
            return f"{offset} min after start"
        elif trigger == EventPlugin.Trigger.AFTER_END:
            if offset == 0:
                return "At end"
            return f"{offset} min after end"
        elif trigger == EventPlugin.Trigger.ON_COMPLETE:
            return "When completed"
        elif trigger == EventPlugin.Trigger.ON_CANCEL:
            return "When canceled"
        return "Unknown"

    def validate(self, attrs):
        """Validate that offset makes sense for the trigger type"""
        trigger = attrs.get('trigger', EventPlugin.Trigger.AT_START)
        offset = attrs.get('offset_minutes', 0)

        # Event-driven triggers don't use offset
        if trigger in [EventPlugin.Trigger.ON_COMPLETE, EventPlugin.Trigger.ON_CANCEL]:
            if offset != 0:
                attrs['offset_minutes'] = 0  # Auto-correct instead of error

        return attrs


class GlobalEventPluginSerializer(serializers.ModelSerializer):
    """
    Serializer for GlobalEventPlugin - rules for auto-attaching plugins to ALL events.

    When created, automatically applies to:
    1. All existing events
    2. All future events as they are created
    """

    plugin_name = serializers.CharField(source='plugin_installation.template.name', read_only=True)
    plugin_description = serializers.CharField(source='plugin_installation.template.short_description', read_only=True)
    plugin_category = serializers.CharField(source='plugin_installation.template.category', read_only=True)
    plugin_logo_url = serializers.CharField(source='plugin_installation.template.logo_url', read_only=True)
    trigger_display = serializers.CharField(source='get_trigger_display', read_only=True)
    timing_description = serializers.SerializerMethodField()
    events_count = serializers.SerializerMethodField()

    class Meta:
        model = GlobalEventPlugin
        fields = [
            'id',
            'plugin_installation',
            'plugin_name',
            'plugin_description',
            'plugin_category',
            'plugin_logo_url',
            'trigger',
            'trigger_display',
            'offset_minutes',
            'timing_description',
            'is_active',
            'apply_to_existing',
            'execution_order',
            'events_count',
            'created_at',
            'updated_at',
            'created_by',
        ]
        read_only_fields = ['id', 'created_at', 'updated_at', 'created_by']

    def get_timing_description(self, obj):
        """Generate a human-readable description of when the plugin runs."""
        trigger = obj.trigger
        offset = obj.offset_minutes

        if trigger == 'before_start':
            if offset == 0:
                return "At start"
            return f"{offset} min before start"
        elif trigger == 'at_start':
            if offset == 0:
                return "At start"
            return f"{offset} min after start"
        elif trigger == 'after_start':
            if offset == 0:
                return "At start"
            return f"{offset} min after start"
        elif trigger == 'after_end':
            if offset == 0:
                return "At end"
            return f"{offset} min after end"
        elif trigger == 'on_complete':
            return "When completed"
        elif trigger == 'on_cancel':
            return "When canceled"
        return "Unknown"

    def get_events_count(self, obj):
        """Get the count of events this rule applies to."""
        return EventPlugin.objects.filter(
            plugin_installation=obj.plugin_installation,
            trigger=obj.trigger,
            offset_minutes=obj.offset_minutes,
        ).count()

    def validate(self, attrs):
        """Validate the global event plugin configuration."""
        trigger = attrs.get('trigger', 'at_start')
        offset = attrs.get('offset_minutes', 0)

        # Event-driven triggers don't use offset
        if trigger in ['on_complete', 'on_cancel']:
            if offset != 0:
                attrs['offset_minutes'] = 0

        return attrs

    def create(self, validated_data):
        """Create the global rule and apply to existing events."""
        # Set the created_by from request context
        request = self.context.get('request')
        if request and hasattr(request, 'user'):
            validated_data['created_by'] = request.user

        return super().create(validated_data)


# =============================================================================
# Time Blocking System Serializers
# =============================================================================

class HolidaySerializer(serializers.ModelSerializer):
    """Serializer for Holiday reference data"""
    next_occurrence = serializers.SerializerMethodField()

    class Meta:
        model = Holiday
        fields = [
            'code', 'name', 'country', 'holiday_type',
            'month', 'day', 'week_of_month', 'day_of_week',
            'calculation_rule', 'is_active', 'next_occurrence',
        ]
        read_only_fields = fields  # Holidays are reference data, not editable via API

    def get_next_occurrence(self, obj):
        """Get the next occurrence date for this holiday"""
        from datetime import date
        today = date.today()
        current_year_date = obj.get_date_for_year(today.year)

        if current_year_date and current_year_date >= today:
            return current_year_date.isoformat()

        # If this year's date has passed, get next year's
        next_year_date = obj.get_date_for_year(today.year + 1)
        return next_year_date.isoformat() if next_year_date else None


class HolidayListSerializer(serializers.ModelSerializer):
    """Lightweight serializer for holiday dropdowns"""

    class Meta:
        model = Holiday
        fields = ['code', 'name', 'country']


class TimeBlockSerializer(LocationRequiredMixin, TimezoneSerializerMixin, serializers.ModelSerializer):
    """Full serializer for TimeBlock CRUD operations.

    Includes timezone context via TimezoneSerializerMixin:
    - business_timezone: IANA timezone string (null = use user's local)
    """
    resource_name = serializers.CharField(source='resource.name', read_only=True, allow_null=True)
    created_by_name = serializers.SerializerMethodField()
    reviewed_by_name = serializers.SerializerMethodField()
    level = serializers.SerializerMethodField()
    pattern_display = serializers.SerializerMethodField()
    holiday_name = serializers.SerializerMethodField()
    conflict_count = serializers.SerializerMethodField()
    # Explicitly declare mixin field for DRF's metaclass
    business_timezone = serializers.SerializerMethodField()

    class Meta:
        model = TimeBlock
        fields = [
            'id', 'title', 'description',
            'resource', 'resource_name', 'level',
            'location', 'is_business_wide',
            'block_type', 'purpose', 'recurrence_type',
            'start_date', 'end_date', 'all_day', 'start_time', 'end_time',
            'recurrence_pattern', 'pattern_display', 'holiday_name',
            'recurrence_start', 'recurrence_end',
            'is_active', 'approval_status', 'reviewed_by', 'reviewed_by_name',
            'reviewed_at', 'review_notes',
            'created_by', 'created_by_name',
            'conflict_count', 'created_at', 'updated_at',
            # Timezone context (from TimezoneSerializerMixin)
            'business_timezone',
        ]
        read_only_fields = ['created_by', 'created_at', 'updated_at', 'reviewed_by', 'reviewed_at',
                          'business_timezone']

    def get_created_by_name(self, obj):
        if obj.created_by:
            return obj.created_by.get_full_name() or obj.created_by.email
        return None

    def get_reviewed_by_name(self, obj):
        if obj.reviewed_by:
            return obj.reviewed_by.get_full_name() or obj.reviewed_by.email
        return None

    def get_level(self, obj):
        """Return 'business' if no resource, otherwise 'resource'"""
        return 'business' if obj.resource is None else 'resource'

    def get_pattern_display(self, obj):
        """Get human-readable description of the recurrence pattern"""
        if obj.recurrence_type == TimeBlock.RecurrenceType.NONE:
            if obj.start_date == obj.end_date:
                return obj.start_date.strftime('%B %d, %Y') if obj.start_date else 'One-time'
            return f"{obj.start_date} to {obj.end_date}" if obj.start_date else 'One-time'

        if obj.recurrence_type == TimeBlock.RecurrenceType.WEEKLY:
            days = obj.recurrence_pattern.get('days_of_week', [])
            day_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
            selected = [day_names[d] for d in days if 0 <= d <= 6]
            return f"Weekly on {', '.join(selected)}" if selected else 'Weekly'

        if obj.recurrence_type == TimeBlock.RecurrenceType.MONTHLY:
            days = obj.recurrence_pattern.get('days_of_month', [])
            if days:
                day_strs = [self._ordinal(d) for d in days]
                return f"Monthly on the {', '.join(day_strs)}"
            return 'Monthly'

        if obj.recurrence_type == TimeBlock.RecurrenceType.YEARLY:
            month = obj.recurrence_pattern.get('month')
            day = obj.recurrence_pattern.get('day')
            if month and day:
                from calendar import month_name
                return f"Yearly on {month_name[month]} {day}"
            return 'Yearly'

        if obj.recurrence_type == TimeBlock.RecurrenceType.HOLIDAY:
            holiday_code = obj.recurrence_pattern.get('holiday_code')
            if holiday_code:
                try:
                    holiday = Holiday.objects.get(code=holiday_code)
                    return f"Holiday: {holiday.name}"
                except Holiday.DoesNotExist:
                    return f"Holiday: {holiday_code}"
            return 'Holiday'

        return str(obj.recurrence_type)

    def _ordinal(self, n):
        """Convert number to ordinal string (1 -> 1st, 2 -> 2nd, etc.)"""
        if 11 <= n <= 13:
            suffix = 'th'
        else:
            suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th')
        return f"{n}{suffix}"

    def get_holiday_name(self, obj):
        """Get holiday name if this is a holiday block"""
        if obj.recurrence_type != TimeBlock.RecurrenceType.HOLIDAY:
            return None
        holiday_code = obj.recurrence_pattern.get('holiday_code')
        if holiday_code:
            try:
                holiday = Holiday.objects.get(code=holiday_code)
                return holiday.name
            except Holiday.DoesNotExist:
                return None
        return None

    def get_conflict_count(self, obj):
        """Count events that conflict with this block"""
        # This is an expensive operation, only compute on detail view
        request = self.context.get('request')
        if request and request.method == 'GET':
            # Only compute for detail endpoints (single object)
            view = self.context.get('view')
            if view and hasattr(view, 'action') and view.action == 'retrieve':
                return self._count_conflicts(obj)
        return None

    def _count_conflicts(self, obj):
        """Count events that would conflict with this time block"""
        from datetime import date, timedelta
        today = date.today()
        end_date = today + timedelta(days=90)  # Look 90 days ahead

        blocked_dates = obj.get_blocked_dates_in_range(today, end_date)
        if not blocked_dates:
            return 0

        # Count events on blocked dates
        from schedule.models import Event
        event_count = 0
        for blocked_date in blocked_dates:
            # Check if any events fall on this date
            events = Event.objects.filter(
                start_time__date=blocked_date,
                status__in=['SCHEDULED', 'CONFIRMED']
            )
            if obj.resource:
                # Filter to events with this resource as participant
                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=obj.resource_id
                )
            event_count += events.count()

        return event_count

    def validate(self, attrs):
        """Validate time block configuration"""
        recurrence_type = attrs.get('recurrence_type', TimeBlock.RecurrenceType.NONE)
        pattern = attrs.get('recurrence_pattern', {})

        # Validate NONE type requires dates
        if recurrence_type == TimeBlock.RecurrenceType.NONE:
            if not attrs.get('start_date'):
                raise serializers.ValidationError({
                    'start_date': 'Start date is required for one-time blocks'
                })

        # Validate WEEKLY requires days_of_week
        if recurrence_type == TimeBlock.RecurrenceType.WEEKLY:
            days = pattern.get('days_of_week', [])
            if not days:
                raise serializers.ValidationError({
                    'recurrence_pattern': 'days_of_week is required for weekly blocks'
                })
            for d in days:
                if not isinstance(d, int) or d < 0 or d > 6:
                    raise serializers.ValidationError({
                        'recurrence_pattern': 'days_of_week must be integers 0-6'
                    })

        # Validate MONTHLY requires days_of_month
        if recurrence_type == TimeBlock.RecurrenceType.MONTHLY:
            days = pattern.get('days_of_month', [])
            if not days:
                raise serializers.ValidationError({
                    'recurrence_pattern': 'days_of_month is required for monthly blocks'
                })
            for d in days:
                if not isinstance(d, int) or d < 1 or d > 31:
                    raise serializers.ValidationError({
                        'recurrence_pattern': 'days_of_month must be integers 1-31'
                    })

        # Validate YEARLY requires month and day
        if recurrence_type == TimeBlock.RecurrenceType.YEARLY:
            if not pattern.get('month') or not pattern.get('day'):
                raise serializers.ValidationError({
                    'recurrence_pattern': 'month and day are required for yearly blocks'
                })
            month = pattern.get('month')
            day = pattern.get('day')
            if not (1 <= month <= 12):
                raise serializers.ValidationError({
                    'recurrence_pattern': 'month must be 1-12'
                })
            if not (1 <= day <= 31):
                raise serializers.ValidationError({
                    'recurrence_pattern': 'day must be 1-31'
                })

        # Validate HOLIDAY requires holiday_code
        if recurrence_type == TimeBlock.RecurrenceType.HOLIDAY:
            holiday_code = pattern.get('holiday_code')
            if not holiday_code:
                raise serializers.ValidationError({
                    'recurrence_pattern': 'holiday_code is required for holiday blocks'
                })
            if not Holiday.objects.filter(code=holiday_code, is_active=True).exists():
                raise serializers.ValidationError({
                    'recurrence_pattern': f"Holiday '{holiday_code}' not found"
                })

        # Validate time fields
        all_day = attrs.get('all_day', True)
        if not all_day:
            if not attrs.get('start_time'):
                raise serializers.ValidationError({
                    'start_time': 'Start time is required when all_day is false'
                })
            if not attrs.get('end_time'):
                raise serializers.ValidationError({
                    'end_time': 'End time is required when all_day is false'
                })
            if attrs.get('start_time') >= attrs.get('end_time'):
                raise serializers.ValidationError({
                    'end_time': 'End time must be after start time'
                })

        # Check permission for hard blocks if user is staff
        request = self.context.get('request')
        if request and request.user.is_authenticated:
            user = request.user
            block_type = attrs.get('block_type', TimeBlock.BlockType.SOFT)
            resource = attrs.get('resource')

            # Staff creating hard blocks need permission
            if user.role == 'TENANT_STAFF':
                if block_type == TimeBlock.BlockType.HARD:
                    permissions = user.permissions or {}
                    if not permissions.get('can_create_hard_blocks', False):
                        raise serializers.ValidationError({
                            'block_type': 'You do not have permission to create hard blocks'
                        })

                # Staff can only create blocks for their own resource
                if resource:
                    if not user.staff_resources.filter(pk=resource.pk).exists():
                        raise serializers.ValidationError({
                            'resource': 'Staff can only create blocks for their own resource'
                        })
                else:
                    # Staff cannot create business-level blocks
                    permissions = user.permissions or {}
                    if not permissions.get('can_create_business_blocks', False):
                        raise serializers.ValidationError({
                            'resource': 'Staff cannot create business-level blocks'
                        })

        # Location validation for TimeBlocks
        # Business-wide blocks: skip (they apply to all locations)
        # Resource-specific blocks: skip (location determined by resource)
        # Location-level blocks (no resource): require location when multi-location enabled
        is_business_wide = attrs.get('is_business_wide')
        if is_business_wide is None and self.instance:
            is_business_wide = self.instance.is_business_wide

        resource = attrs.get('resource')
        if resource is None and self.instance and 'resource' not in attrs:
            resource = self.instance.resource

        # Only validate location for location-level blocks (not business-wide, not resource-specific)
        if not is_business_wide and not resource:
            self._validate_location_required(
                attrs,
                error_message='Location is required for location-level blocks when multiple locations exist.'
            )

        return attrs

    def create(self, validated_data):
        """Set created_by from request context"""
        request = self.context.get('request')
        if request and hasattr(request, 'user') and request.user.is_authenticated:
            validated_data['created_by'] = request.user
        return super().create(validated_data)


class TimeBlockListSerializer(TimezoneSerializerMixin, serializers.ModelSerializer):
    """Serializer for time block lists - includes fields needed for editing.

    Includes timezone context via TimezoneSerializerMixin:
    - business_timezone: IANA timezone string (null = use user's local)
    """
    resource_name = serializers.CharField(source='resource.name', read_only=True, allow_null=True)
    created_by_name = serializers.SerializerMethodField()
    reviewed_by_name = serializers.SerializerMethodField()
    level = serializers.SerializerMethodField()
    pattern_display = serializers.SerializerMethodField()
    # Explicitly declare the mixin field to ensure DRF's metaclass picks it up
    business_timezone = serializers.SerializerMethodField()

    class Meta:
        model = TimeBlock
        fields = [
            'id', 'title', 'description', 'resource', 'resource_name', 'level',
            'block_type', 'recurrence_type', 'start_date', 'end_date',
            'all_day', 'start_time', 'end_time', 'recurrence_pattern',
            'recurrence_start', 'recurrence_end', 'pattern_display',
            'is_active', 'approval_status', 'reviewed_by', 'reviewed_by_name',
            'reviewed_at', 'review_notes',
            'created_by', 'created_by_name', 'created_at',
            'business_timezone',
        ]

    def get_created_by_name(self, obj):
        if obj.created_by:
            return obj.created_by.get_full_name() or obj.created_by.email
        return None

    def get_reviewed_by_name(self, obj):
        if obj.reviewed_by:
            return obj.reviewed_by.get_full_name() or obj.reviewed_by.email
        return None

    def get_level(self, obj):
        return 'business' if obj.resource is None else 'resource'

    def get_pattern_display(self, obj):
        """Simple pattern description for list view"""
        if obj.recurrence_type == TimeBlock.RecurrenceType.NONE:
            return 'One-time'
        return obj.recurrence_type.replace('_', ' ').title()


class BlockedDateSerializer(serializers.Serializer):
    """Serializer for blocked date responses (calendar view)"""
    date = serializers.DateField()
    block_type = serializers.CharField()
    title = serializers.CharField()
    resource_id = serializers.IntegerField(allow_null=True)
    all_day = serializers.BooleanField()
    start_time = serializers.TimeField(allow_null=True)
    end_time = serializers.TimeField(allow_null=True)
    time_block_id = serializers.IntegerField()


class CheckConflictsSerializer(serializers.Serializer):
    """Input serializer for checking block conflicts"""
    recurrence_type = serializers.ChoiceField(choices=TimeBlock.RecurrenceType.choices)
    recurrence_pattern = serializers.JSONField(required=False, default=dict)
    start_date = serializers.DateField(required=False, allow_null=True)
    end_date = serializers.DateField(required=False, allow_null=True)
    resource_id = serializers.IntegerField(required=False, allow_null=True)
    all_day = serializers.BooleanField(default=True)
    start_time = serializers.TimeField(required=False, allow_null=True)
    end_time = serializers.TimeField(required=False, allow_null=True)


# ============================================================================
# Media Gallery Serializers
# ============================================================================

class AlbumSerializer(serializers.ModelSerializer):
    """
    Serializer for Album model.

    Provides read-only computed fields for file_count and cover_url.
    """
    file_count = serializers.IntegerField(read_only=True)
    cover_url = serializers.SerializerMethodField()

    class Meta:
        model = Album
        fields = [
            'id', 'name', 'description', 'cover_image',
            'file_count', 'cover_url', 'created_at', 'updated_at'
        ]
        read_only_fields = ['id', 'created_at', 'updated_at']

    def get_cover_url(self, obj):
        """Get the URL of the cover image."""
        if obj.cover_image and obj.cover_image.file:
            return obj.cover_image.file.url
        # If no explicit cover, use first file in album
        first_file = obj.files.first()
        if first_file and first_file.file:
            return first_file.file.url
        return None

    def to_representation(self, instance):
        """Add file_count if not already annotated."""
        data = super().to_representation(instance)
        # If file_count wasn't annotated in the queryset, compute it
        if data.get('file_count') is None:
            data['file_count'] = instance.files.count()
        return data


class MediaFileSerializer(serializers.ModelSerializer):
    """
    Serializer for MediaFile model.

    Handles file uploads with validation for:
    - File type (images only)
    - File size (max 10MB)
    - Storage quota checking
    """
    url = serializers.SerializerMethodField()
    album_name = serializers.CharField(source='album.name', read_only=True, allow_null=True)

    # For uploads - write only
    file = serializers.ImageField(write_only=True, required=False)

    # Allowed MIME types
    ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
    MAX_FILE_SIZE = 10 * 1024 * 1024  # 10 MB

    class Meta:
        model = MediaFile
        fields = [
            'id', 'file', 'url', 'filename', 'alt_text', 'file_size',
            'width', 'height', 'mime_type', 'album', 'album_name', 'created_at'
        ]
        read_only_fields = ['id', 'filename', 'file_size', 'width', 'height', 'mime_type', 'created_at']

    def get_url(self, obj):
        """Get the URL to access the file."""
        if obj.file:
            return obj.file.url
        return None

    def validate_file(self, value):
        """Validate uploaded file."""
        if not value:
            return value

        # Check file size
        if value.size > self.MAX_FILE_SIZE:
            max_mb = self.MAX_FILE_SIZE / (1024 * 1024)
            file_mb = value.size / (1024 * 1024)
            raise serializers.ValidationError(
                f'File size ({file_mb:.1f} MB) exceeds maximum allowed ({max_mb:.0f} MB).'
            )

        # Check content type
        content_type = value.content_type
        if content_type not in self.ALLOWED_TYPES:
            raise serializers.ValidationError(
                f'File type "{content_type}" is not allowed. '
                f'Allowed types: {", ".join(self.ALLOWED_TYPES)}'
            )

        return value

    def validate(self, attrs):
        """Validate storage quota for new uploads."""
        file = attrs.get('file')
        if file:
            # Check storage quota
            request = self.context.get('request')
            tenant = getattr(request, 'tenant', None) if request else None

            if tenant:
                from smoothschedule.identity.core.services import StorageQuotaService
                can_upload, error = StorageQuotaService.can_upload(tenant, file.size)
                if not can_upload:
                    raise serializers.ValidationError({'file': error})

        return attrs

    def create(self, validated_data):
        """Create MediaFile from uploaded file."""
        from PIL import Image
        from smoothschedule.identity.core.services import StorageQuotaService

        file = validated_data.pop('file')

        # Extract file metadata
        validated_data['filename'] = file.name
        validated_data['file_size'] = file.size
        validated_data['mime_type'] = file.content_type

        # Get image dimensions
        try:
            img = Image.open(file)
            validated_data['width'] = img.width
            validated_data['height'] = img.height
            file.seek(0)  # Reset file pointer after reading
        except Exception:
            # If we can't read dimensions, that's okay
            pass

        # Create the media file
        validated_data['file'] = file
        instance = super().create(validated_data)

        # Update storage usage
        request = self.context.get('request')
        tenant = getattr(request, 'tenant', None) if request else None
        if tenant:
            StorageQuotaService.update_usage(tenant, file.size, 1)

        return instance


class MediaFileUpdateSerializer(serializers.ModelSerializer):
    """
    Serializer for updating MediaFile (alt_text, album).

    Does not allow changing the actual file - must delete and re-upload.
    """
    class Meta:
        model = MediaFile
        fields = ['alt_text', 'album']


class StorageUsageSerializer(serializers.Serializer):
    """Serializer for storage usage response."""
    bytes_used = serializers.IntegerField()
    bytes_total = serializers.IntegerField()
    file_count = serializers.IntegerField()
    percent_used = serializers.FloatField()
    # Human-readable versions
    used_display = serializers.SerializerMethodField()
    total_display = serializers.SerializerMethodField()

    def get_used_display(self, obj):
        """Format bytes_used as human-readable string."""
        return self._format_bytes(obj.get('bytes_used', 0))

    def get_total_display(self, obj):
        """Format bytes_total as human-readable string."""
        return self._format_bytes(obj.get('bytes_total', 0))

    def _format_bytes(self, bytes_val):
        """Convert bytes to human-readable format."""
        if bytes_val >= 1024 * 1024 * 1024:
            return f"{bytes_val / (1024 * 1024 * 1024):.1f} GB"
        elif bytes_val >= 1024 * 1024:
            return f"{bytes_val / (1024 * 1024):.1f} MB"
        elif bytes_val >= 1024:
            return f"{bytes_val / 1024:.1f} KB"
        return f"{bytes_val} B"