from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Q, Max, Prefetch, Case, When, IntegerField
from django.shortcuts import get_object_or_404
from django.db import IntegrityError
from .models import TimeTable, WeekDay
from academics.models import AcademicClass, SubjectTeacher  # Add SubjectTeacher import
from people.models import Teacher
from .serializers import (
    TimeTableSerializer,
    TimeTableCreateUpdateSerializer,
    BulkTimeTableSerializer,
    TimetableExportSerializer,
    SimpleWeekDaySerializer,
    WeekDaySerializer
)
from rest_framework.permissions import IsAuthenticated  # Add this import


class WeekDayViewSet(viewsets.ModelViewSet):
    """
    ViewSet for WeekDay CRUD operations
    """

    queryset = WeekDay.objects.all()
    serializer_class = WeekDaySerializer
    permission_classes = [IsAuthenticated]
    # filter_backends = [
    #     DjangoFilterBackend,
    #     filters.SearchFilter,
    #     filters.OrderingFilter,
    # ]
    filterset_fields = ["is_open", "is_active", "day_code"]
    search_fields = ["day_name", "day_code"]
    ordering_fields = ["day_code", "created_at", "day_name"]
    ordering = ["day_code"]

    def get_serializer_class(self):
        if self.action == "retrieve":
            return WeekDayDetailSerializer
        return WeekDaySerializer

    def get_queryset(self):
        queryset = super().get_queryset()

        # Filter by search query
        search = self.request.query_params.get("search", "")
        if search:
            queryset = queryset.filter(
                Q(day_name__icontains=search) | Q(day_code__icontains=search)
            )

        return queryset

    @action(detail=True, methods=["post"])
    def toggle_active(self, request, pk=None):
        """
        Toggle is_active status
        """
        weekday = self.get_object()
        weekday.is_active = not weekday.is_active
        weekday.save()
        serializer = self.get_serializer(weekday)
        return Response(serializer.data)

    @action(detail=False, methods=["get"])
    def open_days(self, request):
        """
        Get all open days
        """
        open_days = self.get_queryset().filter(is_open=True, is_active=True)
        serializer = self.get_serializer(open_days, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=["get"])
    def by_day_code(self, request):
        """
        Get weekday by day code
        """
        day_code = request.query_params.get("code")
        if not day_code:
            return Response(
                {"error": "day_code parameter is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            weekday = self.get_queryset().get(day_code=day_code)
            serializer = self.get_serializer(weekday)
            return Response(serializer.data)
        except WeekDay.DoesNotExist:
            return Response(
                {"error": f"Weekday with code {day_code} not found"},
                status=status.HTTP_404_NOT_FOUND,
            )


class TimeTableViewSet(viewsets.ModelViewSet):
    """
    ViewSet for TimeTable CRUD operations with Excel-like UI support
    """

    queryset = TimeTable.objects.all()
    permission_classes = [IsAuthenticated]
    filterset_fields = [
        "academic_class",
        "weekday",
        "subject",
        "is_break",
        "is_substitution",
        "is_active",
    ]
    search_fields = [
        "academic_class__standard__name",
        "academic_class__section__name",
        "subject__name",
        "notes",
    ]
    ordering_fields = ["period_number", "start_time", "created_at"]
    ordering = ["academic_class", "weekday", "period_number"]

    def get_serializer_class(self):
        if self.action in ["create", "update", "partial_update"]:
            return TimeTableCreateUpdateSerializer
        elif self.action == "bulk_create":
            return BulkTimeTableSerializer
        return TimeTableSerializer

    def get_queryset(self):
        queryset = (
            TimeTable.objects.filter(is_active=True)
            .select_related(
                "academic_class__standard",
                "academic_class__section",
                "academic_class__academic_year",
                "weekday",
                "subject",
                "subject_teacher__teacher",
                "substitute_teacher",
            )
            .prefetch_related("academic_class__enrollments")
        )

        # Filter by academic class
        class_id = self.request.query_params.get("class_id")
        if class_id:
            queryset = queryset.filter(academic_class_id=class_id)

        # Filter by weekday
        weekday_id = self.request.query_params.get("weekday_id")
        if weekday_id:
            queryset = queryset.filter(weekday_id=weekday_id)

        # Filter by multiple weekdays (for Excel view)
        weekdays = self.request.query_params.getlist("weekdays[]")
        if weekdays:
            queryset = queryset.filter(weekday_id__in=weekdays)

        # Filter by teacher
        teacher_id = self.request.query_params.get("teacher_id")
        if teacher_id:
            queryset = queryset.filter(
                Q(subject_teacher__teacher_id=teacher_id)
                | Q(substitute_teacher_id=teacher_id)
            )

        # Filter by academic year
        academic_year_id = self.request.query_params.get("academic_year_id")
        if academic_year_id:
            queryset = queryset.filter(
                academic_class__academic_year_id=academic_year_id
            )

        return queryset

    def create(self, request, *args, **kwargs):
        """Override create to handle unique constraint errors gracefully"""
        try:
            return super().create(request, *args, **kwargs)
        except IntegrityError as e:
            if "unique_together" in str(e) or "duplicate" in str(e).lower():
                return Response(
                    {
                        "error": "A timetable entry already exists for this class, day, and period."
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )
            raise e

    def update(self, request, *args, **kwargs):
        """Override update to handle unique constraint errors gracefully"""
        try:
            return super().update(request, *args, **kwargs)
        except IntegrityError as e:
            if "unique_together" in str(e) or "duplicate" in str(e).lower():
                return Response(
                    {
                        "error": "A timetable entry already exists for this class, day, and period."
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )
            raise e

    @action(detail=False, methods=["post"])
    def bulk_create(self, request):
        """
        Bulk create timetable entries for Excel-like grid input
        """
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            try:
                timetables = serializer.save()
                return Response(
                    TimeTableSerializer(timetables, many=True).data,
                    status=status.HTTP_201_CREATED,
                )
            except IntegrityError as e:
                return Response(
                    {
                        "error": "One or more timetable entries conflict with existing data."
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=False, methods=["post"])
    def bulk_update(self, request):
        """
        Bulk update multiple timetable entries
        """
        updates = request.data.get("updates", [])
        updated = []
        errors = []

        for update_data in updates:
            try:
                timetable = TimeTable.objects.get(id=update_data["id"])
                serializer = TimeTableCreateUpdateSerializer(
                    timetable, data=update_data, partial=True
                )
                if serializer.is_valid():
                    serializer.save()
                    updated.append(serializer.data)
                else:
                    errors.append(
                        {"id": update_data["id"], "errors": serializer.errors}
                    )
            except TimeTable.DoesNotExist:
                errors.append(
                    {"id": update_data.get("id"), "error": "Timetable entry not found"}
                )
            except IntegrityError as e:
                errors.append(
                    {"id": update_data.get("id"), "error": "Duplicate entry conflict"}
                )

        return Response(
            {"updated": updated, "errors": errors},
            status=status.HTTP_200_OK if not errors else status.HTTP_207_MULTI_STATUS,
        )

    @action(detail=False, methods=["get"])
    def class_timetable(self, request):
        """
        Get complete timetable for a specific class in grid format
        """
        class_id = request.query_params.get("class_id")
        if not class_id:
            return Response(
                {"error": "class_id is required"}, status=status.HTTP_400_BAD_REQUEST
            )

        academic_class = get_object_or_404(AcademicClass, id=class_id)

        # Get all active weekdays
        weekdays = WeekDay.objects.filter(is_active=True).order_by(
            Case(
                When(day_code="MON", then=1),
                When(day_code="TUE", then=2),
                When(day_code="WED", then=3),
                When(day_code="THU", then=4),
                When(day_code="FRI", then=5),
                When(day_code="SAT", then=6),
                When(day_code="SUN", then=7),
                output_field=IntegerField(),
            )
        )

        # Get all periods for this class
        timetables = self.get_queryset().filter(academic_class=academic_class)

        # Build grid data
        grid_data = []
        for weekday in weekdays:
            weekday_periods = timetables.filter(weekday=weekday).order_by(
                "period_number"
            )
            if weekday_periods.exists():
                grid_data.append(
                    {
                        "weekday": {
                            "id": weekday.id,
                            "name": weekday.day_name,
                            "code": weekday.day_code,
                            "is_open": weekday.is_open,
                            "start_time": weekday.start_time,
                            "end_time": weekday.end_time,
                        },
                        "periods": TimeTableSerializer(weekday_periods, many=True).data,
                    }
                )

        return Response(
            {
                "academic_class": {
                    "id": academic_class.id,
                    "name": str(academic_class),
                    "standard": academic_class.standard.name,
                    "section": academic_class.section.code,
                    "academic_year": academic_class.academic_year.name,
                },
                "timetable": grid_data,
            }
        )

    @action(detail=False, methods=["get"])
    def excel_grid(self, request):
        """
        Get data in Excel-like grid format for easy editing
        """
        class_id = request.query_params.get("class_id")
        if not class_id:
            return Response(
                {"error": "class_id is required"}, status=status.HTTP_400_BAD_REQUEST
            )

        academic_class = get_object_or_404(AcademicClass, id=class_id)

        # Get all weekdays
        weekdays = WeekDay.objects.filter(is_active=True).order_by(
            Case(
                When(day_code="MON", then=1),
                When(day_code="TUE", then=2),
                When(day_code="WED", then=3),
                When(day_code="THU", then=4),
                When(day_code="FRI", then=5),
                When(day_code="SAT", then=6),
                When(day_code="SUN", then=7),
            )
        )

        # Get all periods
        timetables = self.get_queryset().filter(academic_class=academic_class)

        # Find max period number
        max_period = timetables.aggregate(max=Max("period_number"))["max"] or 8

        # Create grid
        grid = []
        for period in range(1, max_period + 1):
            row = {"period_number": period}
            # Add default time slots if needed
            period_entry = timetables.filter(period_number=period).first()
            if period_entry:
                row["start_time"] = period_entry.start_time
                row["end_time"] = period_entry.end_time

            for weekday in weekdays:
                period_entry = timetables.filter(
                    weekday=weekday, period_number=period
                ).first()

                if period_entry:
                    # Get teacher_id from subject_teacher
                    teacher_id = None
                    teacher_name = None
                    if (
                        period_entry.subject_teacher
                        and period_entry.subject_teacher.teacher
                    ):
                        teacher_id = period_entry.subject_teacher.teacher.id
                        teacher_name = period_entry.subject_teacher.teacher.full_name

                    row[f"weekday_{weekday.id}"] = {
                        "id": period_entry.id,
                        "subject_id": (
                            period_entry.subject.id if period_entry.subject else None
                        ),
                        "subject_name": (
                            period_entry.subject.name if period_entry.subject else None
                        ),
                        "subject_teacher_id": (
                            period_entry.subject_teacher.id
                            if period_entry.subject_teacher
                            else None
                        ),
                        "teacher_id": teacher_id,
                        "teacher_name": teacher_name,
                        "room_number": period_entry.room_number,
                        "is_break": period_entry.is_break,
                        "is_substitution": period_entry.is_substitution,
                        "substitute_teacher_id": (
                            period_entry.substitute_teacher.id
                            if period_entry.substitute_teacher
                            else None
                        ),
                        "start_time": period_entry.start_time,
                        "end_time": period_entry.end_time,
                        "notes": period_entry.notes,
                    }
                else:
                    row[f"weekday_{weekday.id}"] = None
            grid.append(row)

        return Response(
            {
                "academic_class": {
                    "id": academic_class.id,
                    "name": str(academic_class),
                },
                "weekdays": [
                    {"id": w.id, "name": w.day_name, "code": w.day_code}
                    for w in weekdays
                ],
                "grid": grid,
            }
        )

    @action(detail=False, methods=["post"])
    def save_grid(self, request):
        """
        Save Excel-like grid data
        """
        class_id = request.data.get("class_id")
        grid_data = request.data.get("grid", [])

        if not class_id:
            return Response(
                {"error": "class_id is required"}, status=status.HTTP_400_BAD_REQUEST
            )

        academic_class = get_object_or_404(AcademicClass, id=class_id)
        weekdays = request.data.get("weekdays", [])

        created = []
        updated = []
        deleted = []
        errors = []

        for row in grid_data:
            period_number = row.get("period_number")
            for weekday_data in weekdays:
                weekday_id = weekday_data["id"]
                cell_key = f"weekday_{weekday_id}"
                cell_data = row.get(cell_key)

                if cell_data and (
                    cell_data.get("subject_id") or cell_data.get("is_break")
                ):
                    try:
                        # Prepare defaults
                        defaults = {
                            "subject_id": (
                                cell_data.get("subject_id")
                                if not cell_data.get("is_break")
                                else None
                            ),
                            "room_number": cell_data.get("room_number"),
                            "is_break": cell_data.get("is_break", False),
                            "is_substitution": cell_data.get("is_substitution", False),
                            "substitute_teacher_id": cell_data.get(
                                "substitute_teacher_id"
                            ),
                            "start_time": cell_data.get("start_time")
                            or row.get("start_time"),
                            "end_time": cell_data.get("end_time")
                            or row.get("end_time"),
                            "notes": cell_data.get("notes"),
                            "is_active": True,
                        }

                        # Handle subject_teacher
                        if cell_data.get("subject_teacher_id"):
                            defaults["subject_teacher_id"] = cell_data[
                                "subject_teacher_id"
                            ]
                        elif cell_data.get("teacher_id") and cell_data.get(
                            "subject_id"
                        ):
                            # Try to find or create subject_teacher
                            try:
                                subject_teacher = SubjectTeacher.objects.get(
                                    academic_class=academic_class,
                                    subject_id=cell_data["subject_id"],
                                    teacher_id=cell_data["teacher_id"],
                                    is_active=True,
                                )
                                defaults["subject_teacher_id"] = subject_teacher.id
                            except SubjectTeacher.DoesNotExist:
                                # Don't set subject_teacher if not found
                                pass

                        # Create or update
                        obj, created_flag = TimeTable.objects.update_or_create(
                            academic_class=academic_class,
                            weekday_id=weekday_id,
                            period_number=period_number,
                            defaults=defaults,
                        )

                        if created_flag:
                            created.append(obj.id)
                        else:
                            updated.append(obj.id)

                    except IntegrityError as e:
                        errors.append(
                            {
                                "period": period_number,
                                "weekday": weekday_id,
                                "error": str(e),
                            }
                        )
                    except Exception as e:
                        errors.append(
                            {
                                "period": period_number,
                                "weekday": weekday_id,
                                "error": str(e),
                            }
                        )

                elif cell_data and cell_data.get("id"):
                    # Delete if exists but no subject and not a break
                    try:
                        timetable = TimeTable.objects.get(id=cell_data["id"])
                        deleted.append(timetable.id)
                        timetable.delete()
                    except TimeTable.DoesNotExist:
                        pass

        return Response(
            {
                "message": "Grid saved successfully",
                "created": len(created),
                "updated": len(updated),
                "deleted": len(deleted),
                "errors": errors if errors else None,
            }
        )

    @action(detail=False, methods=["get"])
    def teacher_timetable(self, request):
        """
        Get timetable for a specific teacher
        """
        teacher_id = request.query_params.get("teacher_id")
        if not teacher_id:
            return Response(
                {"error": "teacher_id is required"}, status=status.HTTP_400_BAD_REQUEST
            )

        timetables = (
            self.get_queryset()
            .filter(
                Q(subject_teacher__teacher_id=teacher_id)
                | Q(substitute_teacher_id=teacher_id)
            )
            .order_by("weekday", "period_number")
        )

        # Group by weekday
        weekdays = WeekDay.objects.filter(is_active=True)
        result = []

        for weekday in weekdays:
            weekday_timetables = timetables.filter(weekday=weekday)
            if weekday_timetables.exists():
                result.append(
                    {
                        "weekday": weekday.day_name,
                        "periods": TimeTableSerializer(
                            weekday_timetables, many=True
                        ).data,
                    }
                )

        return Response(result)

    @action(detail=True, methods=["post"])
    def mark_substitution(self, request, pk=None):
        """
        Mark a period as substitution
        """
        timetable = self.get_object()
        substitute_teacher_id = request.data.get("substitute_teacher_id")

        if not substitute_teacher_id:
            return Response(
                {"error": "substitute_teacher_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        substitute_teacher = get_object_or_404(Teacher, id=substitute_teacher_id)

        timetable.is_substitution = True
        timetable.substitute_teacher = substitute_teacher
        timetable.save()

        return Response(TimeTableSerializer(timetable).data)

    @action(detail=False, methods=["get"])
    def export(self, request):
        """
        Export timetable data
        """
        class_id = request.query_params.get("class_id")
        if class_id:
            queryset = self.get_queryset().filter(academic_class_id=class_id)
        else:
            queryset = self.get_queryset()

        serializer = TimetableExportSerializer(queryset, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=["post"])
    def copy_from_class(self, request):
        """
        Copy timetable from one class to another
        """
        from_class_id = request.data.get("from_class_id")
        to_class_id = request.data.get("to_class_id")

        if not from_class_id or not to_class_id:
            return Response(
                {"error": "from_class_id and to_class_id are required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        from_class = get_object_or_404(AcademicClass, id=from_class_id)
        to_class = get_object_or_404(AcademicClass, id=to_class_id)

        # Get all timetables from source class
        source_timetables = TimeTable.objects.filter(
            academic_class=from_class, is_active=True
        )

        copied = []
        errors = []

        for tt in source_timetables:
            try:
                # Check if already exists in destination
                obj, created = TimeTable.objects.update_or_create(
                    academic_class=to_class,
                    weekday=tt.weekday,
                    period_number=tt.period_number,
                    defaults={
                        "subject": tt.subject,
                        "subject_teacher": tt.subject_teacher,
                        "start_time": tt.start_time,
                        "end_time": tt.end_time,
                        "room_number": tt.room_number,
                        "is_break": tt.is_break,
                        "is_substitution": tt.is_substitution,
                        "substitute_teacher": tt.substitute_teacher,
                        "notes": tt.notes,
                        "is_active": True,
                    },
                )
                copied.append(
                    {
                        "id": obj.id,
                        "action": "created" if created else "updated",
                        "period": tt.period_number,
                        "weekday": tt.weekday.day_name,
                    }
                )
            except IntegrityError as e:
                errors.append(
                    {
                        "period": tt.period_number,
                        "weekday": tt.weekday.day_name,
                        "error": "Conflict saving this entry",
                    }
                )
            except Exception as e:
                errors.append(
                    {
                        "period": tt.period_number,
                        "weekday": tt.weekday.day_name,
                        "error": str(e),
                    }
                )

        response_data = {"message": f"Copied {len(copied)} periods", "copied": copied}

        if errors:
            response_data["errors"] = errors
            return Response(response_data, status=status.HTTP_207_MULTI_STATUS)

        return Response(response_data)
