from django.db import transaction
from django.core.exceptions import ValidationError
from typing import Any
import logging

logger = logging.getLogger(__name__)

from rest_framework import viewsets, filters
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from days.models import Day
from classroom.models import Classroom
from .models import Routine, RoutineData
from .serializers import (
    RoutineSerializer, RoutineListSerializer, RoutineDataListSerializer,
    SaveRoutineDataSerializer, RoutineRoomDataSerializer, UpdateRoomSerializer
)
from status.models import Status
from timeslot.models import TimeSlot
from django.conf import settings
from permissions import DynamicModelPermission
import json
import os
def _serialize_timeslot_for_cache(timeslot):
    """Serialize timeslot to match cache file format"""
    return {
        "id": str(timeslot.id),
        "start_time": timeslot.start_time.strftime("%H:%M:%S") if timeslot.start_time else None,
        "end_time": timeslot.end_time.strftime("%H:%M:%S") if timeslot.end_time else None,
        "slot_name": timeslot.slot_name,
        "slug": timeslot.slug,
        "status": timeslot.status.title if timeslot.status else "Available",
        "created_at": timeslot.created_at.isoformat() if timeslot.created_at else None
    }
def _serialize_day_for_cache(day):
    """Serialize day to match cache file format"""
    return {
        "id": str(day.id),
        "name": day.name,
        "slug": day.slug,
        "status": day.status.title if day.status else "Available",
        "created_at": day.created_at.isoformat() if day.created_at else None
    }
from django.http import HttpResponse
from django.template.loader import render_to_string
from io import BytesIO # Import BytesIO for in-memory file handling
from timeslot.models import TimeSlot # Import TimeSlot
from rest_framework import status # Import status for HTTP_404_NOT_FOUND
from django.shortcuts import get_object_or_404 # For cleaner object retrieval
from .utils import generate_routine_pdf # New import
from .services.ocr_service import OCRService
from .services.parser_service import ParserService
# from .services.slot_count_service import SlotCountService # Moved inside upload method
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile

from organization.mixins import TenantViewSetMixin

class RoutineViewSet(TenantViewSetMixin, viewsets.ModelViewSet):
    queryset = Routine.objects.all()
    lookup_field = 'slug'
    permission_classes = [IsAuthenticated, DynamicModelPermission]
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['created_at', 'name', 'routine_year']
    ordering = ['-created_at']

    def get_serializer_class(self):
        if self.action == 'list' or self.action == 'retrieve':
            return RoutineListSerializer
        return RoutineSerializer

    def create(self, request, *args, **kwargs):
        """Override create to return data with slug, semester, and batch"""
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        organization = getattr(request, 'organization', None)
        if organization:
            serializer.save(organization=organization)
        else:
            serializer.save()
        headers = self.get_success_headers(serializer.data)
        
        # Return the created routine using RoutineListSerializer to include slug, semester, and batch
        routine = Routine.objects.get(pk=serializer.instance.pk)
        response_serializer = RoutineListSerializer(routine)
        return Response(response_serializer.data, status=201, headers=headers)

    def update(self, request, *args, **kwargs):
        """Override update to return data with nested objects"""
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        response_serializer = RoutineListSerializer(instance)
        return Response(response_serializer.data)

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

    def _get_use_cache(self, request):
        """Get use_cache parameter from request, default to 0 (database)"""
        use_cache = request.query_params.get('use_cache', '0')
        try:
            return int(use_cache)
        except (ValueError, TypeError):
            return 0
    def _get_days_from_db(self):
        """Get days from database in cache format"""
        days = Day.objects.select_related('status').all()
        return [_serialize_day_for_cache(day) for day in days]
    def _get_timeslots_from_db(self):
        """Get timeslots from database in cache format"""
        timeslots = TimeSlot.objects.select_related('status').all()
        return [_serialize_timeslot_for_cache(timeslots) for timeslots in timeslots]

    @action(detail=False, methods=['get'])
    def days(self, request):
        """Get days data from cache or database"""
        use_cache = self._get_use_cache(request)
        
        if use_cache == 1:
            # Read from cache file
            try:
                cache_file = os.path.join(settings.DATA_CACHE_DIR, 'days.json')
                with open(cache_file, 'r') as f:
                    return Response(json.load(f))
            except:
                return Response([])
        else:
            # Read from database
            return Response(self._get_days_from_db())
            
    @action(detail=False, methods=['get'])
    def timeslots(self, request):
        """Get timeslots data from cache or database"""
        use_cache = self._get_use_cache(request)
        
        if use_cache == 1:
            # Read from cache file
            try:
                cache_file = os.path.join(settings.DATA_CACHE_DIR, 'timeslots.json')
                with open(cache_file, 'r') as f:
                    return Response(json.load(f))
            except:
                return Response([])
        else:
            # Read from database
            return Response(self._get_timeslots_from_db())

    @action(detail=False, methods=['get'])
    def check_name_uniqueness(self, request):
        name = request.query_params.get('name', None)
        if name is None:
            return Response({'error': 'Name parameter is required.'}, status=400)

        is_unique = not Routine.objects.filter(name__iexact=name).exists()
        return Response({'is_unique': is_unique})

    @action(detail=True, methods=['get'])
    def generate_pdf(self, request, slug=None):
        return generate_routine_pdf(slug)

    @action(detail=True, methods=['post'])
    def duplicate(self, request, slug=None):
        original_routine = self.get_object()
        new_name = request.data.get('name')
        
        if not new_name:
            return Response({'error': 'New name is required.'}, status=400)
            
        # Check for uniqueness of the new name
        if Routine.objects.filter(organization=original_routine.organization, name=new_name).exists():
            return Response({'error': 'A routine with this name already exists.'}, status=400)
            
        try:
            with transaction.atomic():
                # Create the new routine
                new_routine = Routine.objects.create(
                    organization=original_routine.organization,
                    name=new_name,
                    department=original_routine.department,
                    batch=original_routine.batch,
                    year=original_routine.year,
                    semester=original_routine.semester,
                    routine_year=original_routine.routine_year,
                    effective_from=original_routine.effective_from,
                    status=Status.objects.get(slug='draft-routine')
                )
                
                # Copy all routine data (slots)
                original_data = RoutineData.objects.filter(routine=original_routine)
                for data in original_data:
                    RoutineData.objects.create(
                        organization=data.organization,
                        routine=new_routine,
                        day_of_week=data.day_of_week,
                        course=data.course,
                        teacher=data.teacher,
                        room=data.room,
                        time_slot=data.time_slot,
                        status=data.status,
                        slot_count=data.slot_count
                    )
                
                # Return the new routine
                serializer = RoutineListSerializer(new_routine)
                return Response(serializer.data, status=201)
        except Exception as e:
            return Response({'error': str(e)}, status=400)

    @action(detail=False, methods=['post'], url_path='upload')
    def upload(self, request):
        """
        Endpoint to upload image/pdf and extract routine data with slot count information.
        Supports both images (JPG, PNG) and PDFs.
        Note: PDF processing requires poppler-utils to be installed on the system.
        """
        from .services.slot_count_service import SlotCountService # Moved import here
        from settings.models import AppSettings
        
        enable_image_processing = AppSettings.get_setting('enable_image_processing', default=True)
        is_enabled = str(enable_image_processing).lower() == 'true' if isinstance(enable_image_processing, str) else bool(enable_image_processing)
        
        if not is_enabled:
            return Response({'error': 'Image processing feature is disabled.'}, status=status.HTTP_403_FORBIDDEN)
            
        uploaded_file = request.FILES.get('file')
        if not uploaded_file:
            return Response({'error': 'No file uploaded.'}, status=status.HTTP_400_BAD_REQUEST)

        # Validate file type
        allowed_extensions = ['.jpg', '.jpeg', '.png', '.pdf']
        file_ext = os.path.splitext(uploaded_file.name)[1].lower()
        if file_ext not in allowed_extensions:
            return Response({'error': f'Unsupported file format. Allowed: {", ".join(allowed_extensions)}'}, status=status.HTTP_400_BAD_REQUEST)

        # Validate file size (e.g., 5MB limit)
        if uploaded_file.size > 5 * 1024 * 1024:
            return Response({'error': 'File size exceeds 5MB limit.'}, status=status.HTTP_400_BAD_REQUEST)

        # Save temporarily
        path = default_storage.save(f'tmp/{uploaded_file.name}', ContentFile(uploaded_file.read()))
        full_path = os.path.join(settings.MEDIA_ROOT, path)

        try:
            # Extract slot count data from image/PDF with OCR
            slot_data = SlotCountService.extract_cells_with_slot_count(full_path)
            with open("test_data/slot_data_views.json", "w") as f:
                json.dump(slot_data, f, indent=4)
            
            # Check for poppler-related errors for PDFs
            if slot_data.get('error') and 'poppler' in slot_data.get('error', '').lower():
                if os.path.exists(full_path):
                    os.remove(full_path)
                logger.error(f"Poppler not installed - required for PDF processing")
                return Response({
                    'error': 'PDF processing requires poppler-utils to be installed. Please use JPG/PNG images instead, or install poppler.',
                    'installation_guide': {
                        'Windows': 'Download from https://github.com/oschwartz10612/poppler-windows/releases/ or use: choco install poppler',
                        'Mac': 'brew install poppler',
                        'Linux': 'sudo apt-get install poppler-utils'
                    }
                }, status=status.HTTP_400_BAD_REQUEST)
            
            # Log slot extraction results
            if slot_data.get('error'):
                logger.warning(f"Slot extraction warning: {slot_data.get('error')}")
            else:
                logger.info(f"Successfully extracted {len(slot_data.get('cells', []))} cells")
                logger.info(f"Base width: {slot_data.get('base_width')}")
                if slot_data.get('structured_data'):
                    logger.info(f"Extracted {len(slot_data.get('structured_data'))} structured entries with day/slot info")

            # OCR Service: Extract text
            extracted_text = OCRService.extract_text(full_path)
            
            # Parser Service: Convert text to structured JSON
            # raw_data = ParserService.parse_routine_text(extracted_text)
            #------------------temp---------------
            raw_data = {
                    "name":           "1st Batch_32.pdf",
                    "routine_year":   "2026",
                    "effective_from": "01-04-2026",
                    "batch":          "1st Batch",
                    "department":     "Department of Computer Science and Engineering",
                    "semester":       "2nd Semester",
                    "year":           "3rd Year",
                    "teachers": [
                        {
                        "first_name": "Md Nazmul",
                        "last_name":  "Hossain",
                        "code":       "NH"
                        },
                        {
                        "first_name": "Sakib",
                        "last_name":  "Imtiaz",
                        "code":       "SI"
                        },
                        {
                        "first_name": "Md. Touhidul",
                        "last_name":  "Islam",
                        "code":       "MTI"
                        },
                        {
                        "first_name": "Saniul Islam",
                        "last_name":  "Anik",
                        "code":       "SIA"
                        },
                        {
                        "first_name": "Md. Arafat Ibne",
                        "last_name":  "Mizan",
                        "code":       "AIM"
                        }
                    ],
                    "courses": [
                        {
                        "code":   "CSE 3200",
                        "name":   "Software Development Project II",
                        "credit": "0.75"
                        },
                        {
                        "code":   "CSE 3201",
                        "name":   "Operating Systems",
                        "credit": "3.00"
                        },
                        {
                        "code":   "CSE 3202",
                        "name":   "Operating Systems Sessional",
                        "credit": "0.75"
                        },
                        {
                        "code":   "CSE 3203",
                        "name":   "Computer Architecture and Design",
                        "credit": "3.00"
                        },
                        {
                        "code":   "CSE 3205",
                        "name":   "Computer Networks",
                        "credit": "3.00"
                        },
                        {
                        "code":   "CSE 3206",
                        "name":   "Computer Networks Sessional",
                        "credit": "1.50"
                        },
                        {
                        "code":   "CSE 3207",
                        "name":   "Peripherals and Interfacings",
                        "credit": "3.00"
                        },
                        {
                        "code":   "CSE 3208",
                        "name":   "Peripherals and Interfacings Sessional",
                        "credit": "0.75"
                        },
                        {
                        "code":   "CSE 3209",
                        "name":   "Artificial Intelligence",
                        "credit": "3.00"
                        },
                        {
                        "code":   "CSE 3210",
                        "name":   "Artificial Intelligence Sessional",
                        "credit": "0.75"
                        }
                    ],
                    "time_slots": [
                        {
                        "start": "9:00",
                        "end":   "9:50",
                        "type":  "class",
                        "title": ""
                        },
                        {
                        "start": "9:50",
                        "end":   "10:40",
                        "type":  "class",
                        "title": ""
                        },
                        {
                        "start": "10:40",
                        "end":   "11:30",
                        "type":  "class",
                        "title": ""
                        },
                        {
                        "start": "11:30",
                        "end":   "11:40",
                        "type":  "break",
                        "title": "Tea Break"
                        },
                        {
                        "start": "11:40",
                        "end":   "12:30",
                        "type":  "class",
                        "title": ""
                        },
                        {
                        "start": "12:30",
                        "end":   "1:20",
                        "type":  "class",
                        "title": ""
                        },
                        {
                        "start": "1:20",
                        "end":   "1:50",
                        "type":  "break",
                        "title": "Lunch Break"
                        },
                        {
                        "start": "1:50",
                        "end":   "2:40",
                        "type":  "class",
                        "title": ""
                        },
                        {
                        "start": "2:40",
                        "end":   "3:30",
                        "type":  "class",
                        "title": ""
                        },
                        {
                        "start": "3:30",
                        "end":   "4:20",
                        "type":  "class",
                        "title": ""
                        },
                        {
                        "start": "4:20",
                        "end":   "5:10",
                        "type":  "class",
                        "title": ""
                        },
                        {
                        "start": "5:10",
                        "end":   "6:00",
                        "type":  "class",
                        "title": ""
                        }
                    ],
                    "routine_data": {
                        "Monday": [
                        {
                            "slot":    0,
                            "course":  "CSE 3201",
                            "teacher": "SI",
                            "room":    "104"
                        },
                        {
                            "slot":    1,
                            "course":  "CSE 3203",
                            "teacher": "NH",
                            "room":    "104"
                        },
                        {
                            "slot":    4,
                            "course":  "CSE 3209",
                            "teacher": "MTI",
                            "room":    "104"
                        }
                        ],
                        "Tuesday": [
                        {
                            "slot":    0,
                            "course":  "CSE 3202",
                            "teacher": "SI",
                            "room":    "305 (Every Alternative Week)"
                        },
                        {
                            "slot":    1,
                            "course":  "CSE 3209",
                            "teacher": "MTI",
                            "room":    "202"
                        },
                        {
                            "slot":    2,
                            "course":  "CSE 3205",
                            "teacher": "SIA",
                            "room":    "202"
                        }
                        ],
                        "Wednesday": [
                        {
                            "slot":    0,
                            "course":  "CSE 3205",
                            "teacher": "SIA",
                            "room":    "204"
                        },
                        {
                            "slot":    1,
                            "course":  "CSE 3201",
                            "teacher": "SI",
                            "room":    "204"
                        },
                        {
                            "slot":    2,
                            "course":  "CSE 3203",
                            "teacher": "NH",
                            "room":    "204"
                        },
                        {
                            "slot":    9,
                            "course":  "CSE 3208",
                            "teacher": "NH",
                            "room":    "302 (Every Alternative Week)"
                        },
                        {
                            "slot":    10,
                            "course":  "CSE 3208",
                            "teacher": "NH",
                            "room":    "302 (Every Alternative Week)"
                        }
                        ],
                        "Thursday": [
                        {
                            "slot":    0,
                            "course":  "CSE 3207",
                            "teacher": "AIM",
                            "room":    "205"
                        },
                        {
                            "slot":    1,
                            "course":  "CSE 3201",
                            "teacher": "SI",
                            "room":    "205"
                        },
                        {
                            "slot":    2,
                            "course":  "CSE 3205",
                            "teacher": "SIA",
                            "room":    "205"
                        },
                        {
                            "slot":    9,
                            "course":  "CSE 3210",
                            "teacher": "MTI",
                            "room":    "305 (Every Alternative Week)"
                        },
                        {
                            "slot":    10,
                            "course":  "CSE 3210",
                            "teacher": "MTI",
                            "room":    "305 (Every Alternative Week)"
                        }
                        ],
                        "Friday": [
                        {
                            "slot":    0,
                            "course":  "CSE 3206",
                            "teacher": "SIA",
                            "room":    "305"
                        },
                        {
                            "slot":    1,
                            "course":  "CSE 3207",
                            "teacher": "AIM",
                            "room":    "302"
                        }
                        ]
                    }
                    }

#-----------------------------end temp----------------------
            # structured_data = ParserService.match_entities(raw_data)
            # logger.info(f"Structured data after entity matching: {structured_data}")
            # Merge slot count information with parsed data
            structured_data = SlotCountService.merge_slot_data_with_parsed_data(raw_data, slot_data)
            with open("test_data/structured_data_views.json", "w") as f:
                json.dump(structured_data, f, indent=4)
            # Clean up temporary file
            if os.path.exists(full_path):
                os.remove(full_path)

            return Response({
                "status": "success",
                "data": structured_data,
                "slot_analysis": {
                    "base_width": slot_data.get('base_width'),
                    "cells_extracted": len(slot_data.get('cells', [])),
                    "structured_entries": len(slot_data.get('structured_data', [])),
                    "extraction_error": slot_data.get('error'),
                    "file_format": file_ext
                }
            })
        except Exception as e:
            # Clean up on error
            if os.path.exists(full_path):
                os.remove(full_path)
            logger.error(f"Error in upload endpoint: {str(e)}")
            return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

class RoutineDataViewSet(TenantViewSetMixin, viewsets.ModelViewSet):
    queryset = RoutineData.objects.all()
    permission_classes = [IsAuthenticated, DynamicModelPermission]

    def check_routine_status(self, routine):
        if not routine.status or routine.status.slug != 'draft-routine':
            raise serializers.ValidationError(
                {"detail": "Only routines in draft status can be modified."}
            )

    def get_queryset(self):
        queryset = super().get_queryset()
        routine_slug = self.request.query_params.get('routine')
        organization = getattr(self.request, 'organization', None)

        if routine_slug:
            if organization:
                # Explicitly validate that the routine belongs to the current organization
                queryset = queryset.filter(
                    routine__slug=routine_slug, routine__organization=organization
                )
            else:
                queryset = queryset.filter(routine__slug=routine_slug)
        return queryset

    def get_serializer_class(self):
        if self.action == 'list' or self.action == 'retrieve':
            return RoutineDataListSerializer
        return SaveRoutineDataSerializer
    
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            routine = serializer.validated_data.get('routine')
            self.check_routine_status(routine)
            
            course = serializer.validated_data.get('course')
            slot_count = serializer.validated_data.get('slot_count')
            effective_slot_count = slot_count if slot_count is not None else (int(course.credit_hours) if course else 1)

            if effective_slot_count > 1:
                # This is a multi-period course, check for conflicts
                all_slots = list(TimeSlot.objects.order_by('start_time'))
                current_slot = serializer.validated_data['time_slot']
                
                try:
                    current_slot_index = all_slots.index(current_slot)
                except ValueError:
                    return Response({'error': 'Current time slot not found in ordered list.'}, status=400)

                additional_slots_needed = effective_slot_count - 1
                slots_to_check = 0
                next_slot_index = current_slot_index + 1
                
                checked_slots = []

                while slots_to_check < additional_slots_needed and next_slot_index < len(all_slots):
                    next_slot = all_slots[next_slot_index]
                    next_slot_index += 1

                    if 'break-time_slot' in next_slot.status.slug:
                        continue

                    checked_slots.append(next_slot)
                    slots_to_check += 1

                if len(checked_slots) < additional_slots_needed:
                    return Response({'error': 'Not enough time slots available in the day for this course.'}, status=400)

                for slot_to_check in checked_slots:
                    if RoutineData.objects.filter(
                        day_of_week=serializer.validated_data['day_of_week'],
                        time_slot=slot_to_check,
                        routine=serializer.validated_data['routine']
                    ).exists():
                        return Response({'error': 'slots is not avialable for this course'}, status=400)


            try:
                with transaction.atomic():
                    existing_qs = RoutineData.objects.filter(
                        day_of_week=serializer.validated_data['day_of_week'],
                        time_slot=serializer.validated_data['time_slot'],
                        routine=serializer.validated_data['routine']
                    )
                    
                    if existing_qs.exists():
                        existing = existing_qs.first()
                        # Before updating, if the course is changing, delete the old hidden slots
                        if existing.course != serializer.validated_data.get('course'):
                            self.delete_hidden_slots(existing)

                        # Update existing record
                        for attr, value in serializer.validated_data.items():
                            if attr != 'slot_count':
                                setattr(existing, attr, value)
                        
                        try:
                            existing.save()
                        except ValidationError as e:
                            error_msg = e.message_dict if hasattr(e, 'message_dict') else e.messages
                            if isinstance(error_msg, dict) and '__all__' in error_msg:
                                error_msg = error_msg['__all__']
                            return Response({'error': error_msg}, status=400)

                        # After saving, we can check for course credit hours and create hidden slots
                        hidden_slots = self.create_hidden_slots(existing, slot_count=slot_count)
                        # Collect all rows: main instance + hidden slots
                        all_rows = [existing] + hidden_slots
                        # Return all rows with status (title and slug) using RoutineDataListSerializer
                        response_serializer = RoutineDataListSerializer(all_rows, many=True)
                        return Response(response_serializer.data, status=200)
                    else:
                        # Create new record
                        try:
                            organization = getattr(self.request, 'organization', None)
                            # Remove slot_count before saving to model
                            save_data = serializer.validated_data.copy()
                            save_data.pop('slot_count', None)
                            
                            new_instance = RoutineData.objects.create(organization=organization, **save_data) if organization else RoutineData.objects.create(**save_data)
                        except ValidationError as e:
                            error_msg = e.message_dict if hasattr(e, 'message_dict') else e.messages
                            if isinstance(error_msg, dict) and '__all__' in error_msg:
                                error_msg = error_msg['__all__']
                            return Response({'error': error_msg}, status=400)
                        
                        # After creating, we can check for course credit hours and create hidden slots
                        hidden_slots = self.create_hidden_slots(new_instance, slot_count=slot_count)
                        # Collect all rows: main instance + hidden slots
                        all_rows = [new_instance] + hidden_slots
                        # Return all rows with status (title and slug) using RoutineDataListSerializer
                        response_serializer = RoutineDataListSerializer(all_rows, many=True)
                        return Response(response_serializer.data, status=201)
            except Exception as e:
                return Response({'error': str(e)}, status=400)
        return Response(serializer.errors, status=400)

    def _iterate_hidden_slots(self, routine_data_instance, slot_count=None):
        effective_slot_count = slot_count if slot_count is not None else (int(routine_data_instance.course.credit_hours) if routine_data_instance.course else 1)
        
        if effective_slot_count <= 1:
            return

        try:
            hidden_status = Status.objects.get(slug='hidden-routine_data')
        except Status.DoesNotExist:
            return

        all_slots = list(TimeSlot.objects.order_by('start_time'))
        try:
            current_slot_index = all_slots.index(routine_data_instance.time_slot)
        except ValueError:
            return

        additional_slots = effective_slot_count - 1
        processed_count = 0
        next_slot_index = current_slot_index + 1

        while processed_count < additional_slots and next_slot_index < len(all_slots):
            next_slot = all_slots[next_slot_index]
            if 'break-time_slot' not in next_slot.status.slug:
                yield next_slot, hidden_status
                processed_count += 1
            next_slot_index += 1

    def create_hidden_slots(self, routine_data_instance, slot_count=None):
        """Create hidden slots and return list of created instances"""
        created_slots = []
        organization = getattr(routine_data_instance, 'organization', None)
        for next_slot, hidden_status in self._iterate_hidden_slots(routine_data_instance, slot_count=slot_count):
            hidden_slot = RoutineData.objects.create(
                routine=routine_data_instance.routine,
                day_of_week=routine_data_instance.day_of_week,
                course=routine_data_instance.course,
                teacher=routine_data_instance.teacher,
                room=routine_data_instance.room,
                time_slot=next_slot,
                status=hidden_status,
                organization=organization
            )
            created_slots.append(hidden_slot)
        return created_slots

    def delete_hidden_slots(self, routine_data_instance):
        for next_slot, hidden_status in self._iterate_hidden_slots(routine_data_instance):
            try:
                hidden_slot = RoutineData.objects.get(
                    routine=routine_data_instance.routine,
                    day_of_week=routine_data_instance.day_of_week,
                    course=routine_data_instance.course,
                    time_slot=next_slot,
                    status=hidden_status
                )
                hidden_slot.delete()
            except RoutineData.DoesNotExist:
                pass  # Slot might not exist, which is fine

    def update_hidden_slots_room(self, routine_data_instance, new_room):
        for next_slot, hidden_status in self._iterate_hidden_slots(routine_data_instance):
            try:
                hidden_slot = RoutineData.objects.get(
                    routine=routine_data_instance.routine,
                    day_of_week=routine_data_instance.day_of_week,
                    course=routine_data_instance.course,
                    time_slot=next_slot,
                    status=hidden_status
                )
                hidden_slot.room = new_room
                hidden_slot.save()
            except RoutineData.DoesNotExist:
                pass
    
    @action(detail=False, methods=['put'])
    def update_room(self, request):
        serializer = UpdateRoomSerializer(data=request.data)
        if not serializer.is_valid():
            return Response(serializer.errors, status=400)
        
        validated_data = serializer.validated_data
        room = validated_data.get('room')
        routine = validated_data.get('routine')
        
        # Check routine status
        self.check_routine_status(routine)
        
        # Validate room exists
        if room and not Classroom.objects.filter(classroom_id=room.classroom_id).exists():
            return Response({'error': 'Room not found'}, status=404)
        
        try:
            routine_data = RoutineData.objects.get(
                day_of_week=validated_data['day_of_week'],
                time_slot=validated_data['time_slot'],
                routine=validated_data['routine']
            )
            
            # The model's save method now handles cross-routine room validation
            try:
                with transaction.atomic():
                    routine_data.room = room
                    routine_data.save()

                    # If this is a main slot, update its hidden slots' room
                    if routine_data.status.slug != 'hidden-routine_data':
                        self.update_hidden_slots_room(routine_data, room)

                return Response({'message': 'Room updated successfully'}, status=200)
            except ValidationError as e:
                # Format the validation error for the API response
                error_msg = e.message_dict if hasattr(e, 'message_dict') else str(e)
                if isinstance(error_msg, dict) and '__all__' in error_msg:
                    error_msg = error_msg['__all__']
                return Response({'error': error_msg}, status=400)
            except Exception as e:
                return Response({'error': str(e)}, status=400)
        except RoutineData.DoesNotExist:
            return Response({'error': 'Routine data not found'}, status=404)
    
    @action(detail=False, methods=['delete'])
    def delete_slot(self, request):
        day_of_week = request.data.get('day_of_week') or request.query_params.get('day_of_week')
        time_slot = request.data.get('time_slot') or request.query_params.get('time_slot')
        routine_slug = request.data.get('routine') or request.query_params.get('routine')
        
        if not all([day_of_week, time_slot, routine_slug]):
            return Response({'error': 'day_of_week, time_slot, and routine are required.'}, status=400)

        try:
            routine = Routine.objects.get(slug=routine_slug)
            self.check_routine_status(routine)
            
            matching_routine_data = RoutineData.objects.filter(
                day_of_week=day_of_week,
                time_slot=time_slot,
                routine=routine
            )
            
            # If no available slot found, try without the available status filter 
            # to see if it's a hidden slot or something else that should be cleared
            if not matching_routine_data.exists():
                 return Response({'error': 'Routine data not found'}, status=404)

            deleted_count = 0
            for routine_data_instance in matching_routine_data:
                self.delete_hidden_slots(routine_data_instance)
                routine_data_instance.delete()
                deleted_count += 1
                
            return Response({'message': f'{deleted_count} routine data entries deleted successfully'}, status=204)
        except Exception as e:
            return Response({'error': str(e)}, status=400)
    
    @action(detail=False, methods=['get'])
    def filtered(self, request):
        teacher = request.query_params.get('teacher')
        department = request.query_params.get('department')
        classroom = request.query_params.get('classroom')
        
        # Start with a base queryset that only includes published routines
        queryset = RoutineData.objects.filter(routine__status__slug='published-routine')
        if teacher:
            queryset = queryset.filter(teacher__slug=teacher)
        elif department:
            queryset = queryset.filter(routine__department__slug=department)
        elif classroom:
            queryset = queryset.filter(room__slug=classroom)
        else:
            return Response([], status=200)
        
        # Add select_related to optimize queries
        queryset = queryset.select_related(
            'teacher',
            'routine',
            'course',
            'room',
            'routine__status',
            'routine__department'
        )
        
        serializer = RoutineDataSerializer(queryset, many=True)
        return Response(serializer.data)
    