from django.db.models import Count, IntegerField, OuterRef, Prefetch, Subquery
from django.db.models.functions import Coalesce
from rest_framework import viewsets
from drf_spectacular.utils import extend_schema, OpenApiParameter

from .models import (
    CarBooking, Reimbursement, ReimbursementReceipt, PaymentRequest, DocumentRequest,
    LetterPrefix, LetterNumber, Announcement, SOPCategory, SOP, SOPVersion, InternalGuide,
    HelpdeskTicket
)
from .serializers import (
    CarBookingSerializer, ReimbursementSerializer, ReimbursementReceiptSerializer,
    PaymentRequestSerializer,
    DocumentRequestSerializer, LetterPrefixSerializer, LetterNumberSerializer,
    AnnouncementSerializer, SOPCategorySerializer, SOPSerializer, InternalGuideSerializer,
    HelpdeskTicketSerializer
)

from rest_framework.permissions import IsAuthenticated

from apps.core.pagination import StandardResultsSetPagination
from apps.core.permissions import HasMenuPermission, IsOwnerOrFullScope, user_has_full_scope
from apps.core.tenancy import get_active_organization, TenantScopedMixin


def _notify_administrations(user, *, title, message, link="", priority="medium", related_user=None):
    """Fire an in-app administrations notification; never break the request on failure."""
    if not user:
        return
    try:
        from apps.notifications.handler import NotificationHandler
        NotificationHandler.send(
            user=user, title=title, message=message,
            notification_type="administrations", priority=priority, icon="clipboard-list",
            link=link, related_user=related_user, broadcast=True,
        )
    except Exception:
        pass


class OwnRequestScopedMixin(TenantScopedMixin):
    """Self-service request lists: users without full administrations scope see
    only their own (`requester`) records; managers see all. Object-level writes
    are guarded by IsOwnerOrFullScope."""
    permission_classes = [IsAuthenticated, HasMenuPermission, IsOwnerOrFullScope]
    pagination_class = StandardResultsSetPagination

    def get_queryset(self):
        qs = super().get_queryset()
        user = self.request.user
        if not user_has_full_scope(user, "administrations", get_active_organization(self.request)):
            qs = qs.filter(requester=user)
        requester_id = self.request.query_params.get("requester")
        if requester_id:
            qs = qs.filter(requester_id=requester_id)
        return qs


class CarBookingViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """ViewSet for CarBooking management."""
    rbac_domain = "administrations"
    permission_classes = [IsAuthenticated, HasMenuPermission, IsOwnerOrFullScope]
    queryset = CarBooking.objects.select_related("requester", "approved_by", "vehicle")
    serializer_class = CarBookingSerializer
    pagination_class = StandardResultsSetPagination

    def get_queryset(self):
        qs = super().get_queryset()
        user = self.request.user
        # Users without full administrations scope only see their own bookings.
        if not user_has_full_scope(user, "administrations", get_active_organization(self.request)):
            qs = qs.filter(requester=user)
        requester_id = self.request.query_params.get("requester")
        if requester_id:
            qs = qs.filter(requester_id=requester_id)
        status_param = self.request.query_params.get("status")
        if status_param:
            qs = qs.filter(status=status_param)
        return qs

    def perform_create(self, serializer):
        serializer.save(requester=self.request.user, organization=get_active_organization(self.request))

    def perform_update(self, serializer):
        prev_status = serializer.instance.status
        obj = serializer.save()
        if obj.status != prev_status and obj.status in ("approved", "rejected"):
            _notify_administrations(
                obj.requester,
                title=f"Car booking {obj.status}",
                message=f"Your car booking to \"{obj.destination}\" was {obj.status}.",
                link="/administrations/car-bookings",
                priority="medium",
                related_user=self.request.user if self.request.user.is_authenticated else None,
            )

    @extend_schema(
        parameters=[
            OpenApiParameter(name="status", type=str, description="Filter by status"),
            OpenApiParameter(name="requester", type=str, description="Filter by requester ID"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class ReimbursementViewSet(OwnRequestScopedMixin, viewsets.ModelViewSet):
    """ViewSet for Reimbursement management."""
    rbac_domain = "administrations"
    queryset = Reimbursement.objects.select_related("requester", "approved_by").prefetch_related(
        "receipt_files__uploaded_by"
    )
    serializer_class = ReimbursementSerializer

    def perform_create(self, serializer):
        serializer.save(requester=self.request.user, organization=get_active_organization(self.request))

    def perform_update(self, serializer):
        prev_status = serializer.instance.status
        obj = serializer.save()
        if obj.status != prev_status and obj.status in ("approved", "rejected"):
            _notify_administrations(
                obj.requester,
                title=f"Reimbursement {obj.status}",
                message=f"Your reimbursement \"{obj.title}\" was {obj.status}.",
                link="/administrations/reimbursements",
                priority="medium",
                related_user=self.request.user if self.request.user.is_authenticated else None,
            )

    @extend_schema(
        parameters=[
            OpenApiParameter(name="status", type=str, description="Filter by status"),
            OpenApiParameter(name="requester", type=str, description="Filter by requester ID"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class ReimbursementReceiptViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """ViewSet for reimbursement receipt files (image / PDF uploads)."""
    rbac_domain = "administrations"
    queryset = ReimbursementReceipt.objects.select_related("uploaded_by")
    serializer_class = ReimbursementReceiptSerializer
    pagination_class = StandardResultsSetPagination

    def get_queryset(self):
        qs = super().get_queryset()
        reimbursement_id = self.request.query_params.get("reimbursement")
        if reimbursement_id:
            qs = qs.filter(reimbursement_id=reimbursement_id)
        return qs

    def perform_create(self, serializer):
        from apps.core.uploads import validate_upload
        upload = self.request.FILES.get("file")
        if upload is not None:
            validate_upload(upload)
        serializer.save(uploaded_by=self.request.user, organization=get_active_organization(self.request))

    @extend_schema(
        parameters=[
            OpenApiParameter(name="reimbursement", type=str, description="Filter by reimbursement ID"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class PaymentRequestViewSet(OwnRequestScopedMixin, viewsets.ModelViewSet):
    """ViewSet for PaymentRequest management."""
    rbac_domain = "administrations"
    queryset = PaymentRequest.objects.select_related("requester", "approved_by", "source_invoice")
    serializer_class = PaymentRequestSerializer

    def perform_create(self, serializer):
        serializer.save(requester=self.request.user, organization=get_active_organization(self.request))

    def perform_update(self, serializer):
        prev_status = serializer.instance.status
        obj = serializer.save()
        if obj.status != prev_status and obj.status in ("approved", "rejected"):
            _notify_administrations(
                obj.requester,
                title=f"Payment request {obj.status}",
                message=f"Your payment request \"{obj.title}\" was {obj.status}.",
                link="/administrations/payment-requests",
                priority="medium",
                related_user=self.request.user if self.request.user.is_authenticated else None,
            )

    @extend_schema(
        parameters=[
            OpenApiParameter(name="status", type=str, description="Filter by status"),
            OpenApiParameter(name="requester", type=str, description="Filter by requester ID"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class DocumentRequestViewSet(OwnRequestScopedMixin, viewsets.ModelViewSet):
    """ViewSet for DocumentRequest management."""
    rbac_domain = "administrations"
    # full_number reads letter.prefix.code — select it inside the prefetch or
    # each serialized row re-queries the prefix.
    queryset = DocumentRequest.objects.select_related("requester").prefetch_related(
        Prefetch("letters", queryset=LetterNumber.objects.select_related("prefix"))
    )
    serializer_class = DocumentRequestSerializer

    def perform_create(self, serializer):
        serializer.save(requester=self.request.user, organization=get_active_organization(self.request))

    @extend_schema(
        parameters=[
            OpenApiParameter(name="status", type=str, description="Filter by status"),
            OpenApiParameter(name="requester", type=str, description="Filter by requester ID"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class LetterPrefixViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """ViewSet for LetterPrefix (department/unit letter types). Managed by secretariat."""
    rbac_domain = "administrations"
    # letter_count_anno feeds LetterPrefixSerializer.get_letter_count (avoids per-row count()).
    # `code` (Meta.ordering) isn't unique — `pk` is the tie-breaker pagination needs.
    queryset = LetterPrefix.objects.annotate(letter_count_anno=Count("letters")).order_by("code", "pk")
    serializer_class = LetterPrefixSerializer
    pagination_class = StandardResultsSetPagination


class LetterNumberViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """ViewSet for LetterNumber management."""
    rbac_domain = "administrations"
    queryset = LetterNumber.objects.select_related("prefix", "document_request")
    serializer_class = LetterNumberSerializer
    pagination_class = StandardResultsSetPagination

    def perform_create(self, serializer):
        from django.db.models import Max
        organization = get_active_organization(self.request)
        prefix = serializer.validated_data["prefix"]
        year = serializer.validated_data["year"]
        # Sequence continues per prefix within a year, resets each new year.
        last = (
            LetterNumber.objects
            .filter(organization=organization, prefix=prefix, year=year)
            .aggregate(m=Max("sequence"))["m"]
        )
        letter = serializer.save(sequence=(last or 0) + 1, organization=organization)
        # Issuing a letter for a request marks that request fulfilled.
        if letter.document_request_id:
            req = letter.document_request
            if req.status != "ready":
                req.status = "ready"
                req.save(update_fields=["status"])

    @extend_schema(
        parameters=[
            OpenApiParameter(name="prefix", type=str, description="Filter by prefix"),
            OpenApiParameter(name="year", type=int, description="Filter by year"),
            OpenApiParameter(name="month", type=int, description="Filter by month"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class AnnouncementViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """ViewSet for Announcement management."""
    rbac_domain = "administrations"
    queryset = Announcement.objects.select_related("author")
    serializer_class = AnnouncementSerializer
    pagination_class = StandardResultsSetPagination

    def perform_create(self, serializer):
        announcement = serializer.save(author=self.request.user, organization=get_active_organization(self.request))
        self._notify_employees(announcement)

    def _notify_employees(self, announcement):
        from apps.notifications.handler import NotificationHandler
        from apps.hr.models import Employee
        from django.contrib.auth import get_user_model
        user_ids = (
            Employee.objects.filter(status="active", organization=announcement.organization)
            .exclude(user__isnull=True)
            .exclude(user_id=announcement.author_id)
            .values_list("user_id", flat=True)
            .distinct()
        )
        User = get_user_model()
        users = list(User.objects.filter(id__in=list(user_ids)))
        if not users:
            return
        try:
            NotificationHandler.broadcast(
                users=users,
                title=announcement.title,
                message=f"New announcement: {announcement.get_priority_display()}.",
                notification_type="administrations",
                icon="megaphone",
                link=f"/administrations/announcements/{announcement.id}",
                metadata={"announcement_id": str(announcement.id)},
            )
        except Exception:
            pass


class SOPCategoryViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """ViewSet for SOPCategory (managed SOP category list)."""
    rbac_domain = "administrations"
    # SOP.category stores the category *name* (no FK), so count via subquery.
    # sop_count_anno feeds SOPCategorySerializer.get_sop_count (avoids per-row count()).
    queryset = SOPCategory.objects.annotate(
        sop_count_anno=Coalesce(
            Subquery(
                SOP.objects.filter(category=OuterRef("name"))
                .order_by().values("category")
                .annotate(c=Count("id")).values("c"),
                output_field=IntegerField(),
            ),
            0,
        )
    )
    serializer_class = SOPCategorySerializer
    pagination_class = StandardResultsSetPagination


class SOPViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """ViewSet for SOP management. HR authors SOPs; all staff can read them."""
    rbac_domain = "administrations"
    queryset = SOP.objects.select_related("author", "approving_authority__user").prefetch_related("versions__changed_by")
    serializer_class = SOPSerializer
    pagination_class = StandardResultsSetPagination

    @extend_schema(
        parameters=[
            OpenApiParameter(name="category", type=str, description="Filter by category"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    def perform_create(self, serializer):
        change_note = serializer.validated_data.pop("change_note", "")
        user = self.request.user if self.request.user.is_authenticated else None
        sop = serializer.save(author=user, organization=get_active_organization(self.request))
        SOPVersion.objects.create(
            sop=sop, version=sop.version, content=sop.content,
            effective_date=sop.effective_date,
            change_note=change_note or "Initial version", changed_by=user,
        )

    def perform_update(self, serializer):
        change_note = serializer.validated_data.pop("change_note", "")
        old_version = serializer.instance.version
        sop = serializer.save()
        if sop.version != old_version:
            SOPVersion.objects.create(
                sop=sop, version=sop.version, content=sop.content,
                effective_date=sop.effective_date,
                change_note=change_note or f"Updated to {sop.version}",
                changed_by=self.request.user if self.request.user.is_authenticated else None,
            )


class HelpdeskTicketViewSet(OwnRequestScopedMixin, viewsets.ModelViewSet):
    """ViewSet for HelpdeskTicket management. Users see their own tickets;
    managers with full administrations scope see all."""
    rbac_domain = "administrations"
    queryset = HelpdeskTicket.objects.select_related("requester", "assigned_to")
    serializer_class = HelpdeskTicketSerializer

    def get_queryset(self):
        qs = super().get_queryset()
        status_param = self.request.query_params.get("status")
        if status_param:
            qs = qs.filter(status=status_param)
        return qs

    def perform_create(self, serializer):
        serializer.save(requester=self.request.user, organization=get_active_organization(self.request))

    def perform_update(self, serializer):
        prev_assigned_id = serializer.instance.assigned_to_id
        ticket = serializer.save()
        actor = self.request.user if self.request.user.is_authenticated else None
        if (
            ticket.assigned_to_id
            and ticket.assigned_to_id != prev_assigned_id
            and (not actor or ticket.assigned_to_id != actor.id)
        ):
            _notify_administrations(
                ticket.assigned_to,
                title="Helpdesk ticket assigned",
                message=f"You were assigned ticket \"{ticket.subject}\".",
                link=f"/administrations/helpdesk/{ticket.id}",
                priority="high",
                related_user=actor,
            )

    @extend_schema(
        parameters=[
            OpenApiParameter(name="status", type=str, description="Filter by status"),
            OpenApiParameter(name="requester", type=str, description="Filter by requester ID"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class InternalGuideViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """ViewSet for InternalGuide management."""
    rbac_domain = "administrations"
    queryset = InternalGuide.objects.select_related("author")
    serializer_class = InternalGuideSerializer
    pagination_class = StandardResultsSetPagination

    def perform_create(self, serializer):
        serializer.save(author=self.request.user, organization=get_active_organization(self.request))

    @extend_schema(
        parameters=[
            OpenApiParameter(name="category", type=str, description="Filter by category"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)
