from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes

from .pagination import StandardResultsSetPagination
from .tenancy import (
    TenantScopedMixin,
    assert_object_in_organization,
    get_active_organization,
    organization_of,
)
from .models import User, Tag, Attachment, Comment, Notification, AuditLog, AppConfig
from .serializers import (
    UserSerializer, TagSerializer, AttachmentSerializer,
    CommentSerializer, NotificationSerializer, AuditLogSerializer, AppConfigSerializer
)


class UserViewSet(viewsets.ModelViewSet):
    """ViewSet for User management. Staff: full access. Non-staff: read-only directory + edit self.

    The directory is scoped to the caller's active organization — a user is
    only visible to co-members. Without this filter every authenticated user
    could enumerate every user on the deployment (SEC-01).
    """
    queryset = User.objects.prefetch_related("user_roles__role__permissions")
    serializer_class = UserSerializer

    def get_queryset(self):
        qs = super().get_queryset()
        # `me` serializes request.user directly and must keep working even for
        # a user with no membership yet (fresh signup, pending invite).
        if self.action == "me":
            return qs
        return qs.filter(organizations=get_active_organization(self.request)).distinct()

    def get_permissions(self):
        from rest_framework.permissions import IsAuthenticated, IsAdminUser
        if self.action in ("create", "destroy"):
            return [IsAdminUser()]
        return [IsAuthenticated()]

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        if instance.pk != request.user.pk and not (request.user.is_staff or request.user.is_superuser):
            return Response({"error": "may only update own profile"}, status=403)
        return super().update(request, *args, **kwargs)

    def partial_update(self, request, *args, **kwargs):
        instance = self.get_object()
        if instance.pk != request.user.pk and not (request.user.is_staff or request.user.is_superuser):
            return Response({"error": "may only update own profile"}, status=403)
        return super().partial_update(request, *args, **kwargs)

    @extend_schema(
        summary="Get current user",
        description="Returns the currently authenticated user's details",
        responses={200: UserSerializer}
    )
    @action(detail=False, methods=["get"])
    def me(self, request):
        """Get current authenticated user."""
        serializer = self.get_serializer(request.user)
        return Response(serializer.data)


class TagViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """
    ViewSet for Tag management.

    retrieve:
    Get a specific tag by ID.

    list:
    List all tags with optional filtering by name.

    create:
    Create a new tag.

    update:
    Update an existing tag.

    destroy:
    Delete a tag.
    """
    queryset = Tag.objects.all()
    serializer_class = TagSerializer

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="name",
                type=OpenApiTypes.STR,
                description="Filter tags by name (partial match)"
            ),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class AttachmentViewSet(TenantScopedMixin, viewsets.ModelViewSet):
    """
    ViewSet for Attachment management.

    retrieve:
    Get a specific attachment by ID.

    list:
    List all attachments.

    create:
    Upload a new attachment.

    destroy:
    Delete an attachment.
    """
    queryset = Attachment.objects.all()
    serializer_class = AttachmentSerializer


class CommentViewSet(viewsets.ModelViewSet):
    """ViewSet for Comment management. List/retrieve require `model` and
    `object_id` query params (e.g. ?model=projects.projectfund&object_id=...)
    — comments are always viewed in the context of the entity they're on,
    never as a global feed.

    Comment itself has no organization column, so authorization is delegated to
    the entity the comment hangs off: the target row must resolve to the
    caller's active organization. Without that check the client-supplied
    `model`/`object_id` pair was trusted outright and read another tenant's
    comment thread (SEC-02).
    """
    queryset = Comment.objects.select_related("author", "content_type")
    serializer_class = CommentSerializer

    def get_queryset(self):
        qs = super().get_queryset()
        model = self.request.query_params.get("model")
        object_id = self.request.query_params.get("object_id")
        if not model or not object_id:
            return qs.none()
        from django.contrib.contenttypes.models import ContentType
        app_label, _, model_name = model.partition(".")
        try:
            content_type = ContentType.objects.get_by_natural_key(app_label, model_name)
        except ContentType.DoesNotExist:
            return qs.none()
        # Empty rather than 403: a permission error here would confirm that the
        # target exists in another organization.
        target = content_type.model_class()._default_manager.filter(pk=object_id).first()
        if target is None:
            return qs.none()
        owner = organization_of(target)
        if owner is None or owner.id != get_active_organization(self.request).id:
            return qs.none()
        return qs.filter(content_type=content_type, object_id=object_id)

    def perform_create(self, serializer):
        content_type = serializer.validated_data["model"]
        object_id = serializer.validated_data["object_id"]
        target = content_type.model_class()._default_manager.filter(pk=object_id).first()
        if target is None:
            raise NotFound("Target object does not exist.")
        assert_object_in_organization(target, get_active_organization(self.request))
        serializer.save(author=self.request.user)


class NotificationViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Notification management.

    retrieve:
    Get a specific notification by ID.

    list:
    List all notifications for the current user.

    create:
    Create a new notification.

    update:
    Update a notification (mark as read).

    destroy:
    Delete a notification.
    """
    serializer_class = NotificationSerializer

    def get_queryset(self):
        return Notification.objects.filter(user=self.request.user)

    @extend_schema(
        summary="Mark notification as read",
        description="Mark a specific notification as read"
    )
    @action(detail=True, methods=["post"])
    def mark_read(self, request, pk=None):
        """Mark notification as read."""
        notification = self.get_object()
        notification.is_read = True
        notification.save()
        return Response({"status": "marked as read"})


class AuditLogViewSet(TenantScopedMixin, viewsets.ReadOnlyModelViewSet):
    """ViewSet for AuditLog (read-only). Staff/superuser only — audit data is sensitive."""
    queryset = AuditLog.objects.select_related("user")
    serializer_class = AuditLogSerializer
    pagination_class = StandardResultsSetPagination

    def get_permissions(self):
        from rest_framework.permissions import IsAdminUser
        return [IsAdminUser()]

    def get_queryset(self):
        qs = super().get_queryset()
        user = self.request.query_params.get("user")
        if user:
            qs = qs.filter(user_id=user)
        action_ = self.request.query_params.get("action")
        if action_:
            qs = qs.filter(action=action_)
        model_name = self.request.query_params.get("model_name")
        if model_name:
            qs = qs.filter(model_name=model_name)
        return qs

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="user",
                type=OpenApiTypes.UUID,
                description="Filter by user ID"
            ),
            OpenApiParameter(
                name="action",
                type=OpenApiTypes.STR,
                description="Filter by action type (e.g., 'create', 'update', 'delete')"
            ),
            OpenApiParameter(
                name="model_name",
                type=OpenApiTypes.STR,
                description="Filter by model name"
            ),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class AppConfigViewSet(viewsets.ViewSet):
    """Singleton system config (AI + email). Superuser/staff only — holds secrets."""

    def get_permissions(self):
        from rest_framework.permissions import IsAdminUser
        return [IsAdminUser()]

    def _get_singleton(self):
        obj = AppConfig.objects.first()
        if obj is None:
            obj = AppConfig.objects.create()
        return obj

    @action(detail=False, methods=["get", "patch", "put"], url_path="current")
    def current(self, request):
        obj = self._get_singleton()
        if request.method in ("PATCH", "PUT"):
            serializer = AppConfigSerializer(obj, data=request.data, partial=request.method == "PATCH")
            serializer.is_valid(raise_exception=True)
            serializer.save()
            return Response(serializer.data)
        return Response(AppConfigSerializer(obj).data)