from django.db.models import Count
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema, OpenApiParameter

from .models import Task, Message, Approval
from .serializers import TaskSerializer, MessageSerializer, ApprovalSerializer


class TaskViewSet(viewsets.ModelViewSet):
    """ViewSet for Task management. Scoped: tasks user created, is assigned to, or owns/teams the project for."""
    rbac_domain = "tasks"
    serializer_class = TaskSerializer
    parser_classes = [JSONParser, MultiPartParser, FormParser]

    def get_queryset(self):
        from django.db.models import Q
        from apps.projects.models import ProjectTeamMember
        qs = (
            Task.objects
            .select_related("assignee", "project", "event", "created_by")
            .annotate(msg_count=Count("messages"))
        )
        user = self.request.user
        if not user.is_authenticated:
            return qs.none()
        from apps.core.permissions import user_has_full_scope
        if user_has_full_scope(user, "tasks"):
            return qs
        member_projects = ProjectTeamMember.objects.filter(user=user).values("project_id")
        return qs.filter(
            Q(created_by=user)
            | Q(assignee=user)
            | Q(project_id__in=member_projects)
            | Q(project__lead__user=user)
        ).distinct()

    def perform_create(self, serializer):
        task = serializer.save(created_by=self.request.user)
        if task.assignee_id and task.assignee_id != self.request.user.id:
            self._notify_assigned(task, self.request.user)

    def perform_update(self, serializer):
        prev_assignee_id = serializer.instance.assignee_id
        task = serializer.save()
        # Notify only on a real reassignment to a different person.
        if (
            task.assignee_id
            and task.assignee_id != prev_assignee_id
            and task.assignee_id != self.request.user.id
        ):
            self._notify_assigned(task, self.request.user)

    @staticmethod
    def _notify_assigned(task, actor):
        from apps.notifications.handler import NotificationHandler
        actor_name = actor.get_full_name() or actor.email
        NotificationHandler.send(
            user=task.assignee,
            title="New task assigned",
            message=f"{actor_name} assigned you the task “{task.title}”.",
            notification_type="task",
            priority="high",
            icon="check-square",
            link=f"/tasks/{task.id}",
            related_user=actor,
            broadcast=True,
        )

    # Tasks auto-archive this many days after the assignee approves them.
    AUTO_ARCHIVE_DAYS = 10

    @classmethod
    def _auto_archive_eligible(cls):
        """Archive any task approved more than AUTO_ARCHIVE_DAYS ago. Runs lazily on list-fetch."""
        from datetime import timedelta
        from django.utils import timezone
        cutoff = timezone.now() - timedelta(days=cls.AUTO_ARCHIVE_DAYS)
        (
            Task.objects
            .filter(approved_at__isnull=False, approved_at__lte=cutoff)
            .exclude(status="archived")
            .update(status="archived", archived_at=timezone.now())
        )

    @staticmethod
    def _can_modify_task(user, task) -> bool:
        from apps.core.permissions import user_has_full_scope
        if user_has_full_scope(user, "tasks"):
            return True
        if task.created_by_id == user.id or task.assignee_id == user.id:
            return True
        # Project members may modify project tasks
        if task.project_id:
            from apps.projects.models import ProjectTeamMember
            if ProjectTeamMember.objects.filter(project_id=task.project_id, user_id=user.id).exists():
                return True
        return False

    @action(detail=True, methods=["post"], url_path="attachments")
    def add_attachment(self, request, pk=None):
        """Upload a file with title + description metadata. Single file per request."""
        from apps.core.models import Attachment, AuditLog
        from apps.core.uploads import validate_upload

        task = self.get_object()
        if not self._can_modify_task(request.user, task):
            return Response({"error": "forbidden"}, status=403)
        files = request.FILES.getlist("files") or request.FILES.getlist("file")
        if not files:
            return Response({"error": "no file uploaded"}, status=400)
        for f in files:
            validate_upload(f)

        title = (request.data.get("title") or "").strip()
        description = (request.data.get("description") or "").strip()

        existing = list(task.attachments or [])
        for f in files:
            att = Attachment.objects.create(
                file=f,
                name=f.name,
                size=f.size,
                mime_type=getattr(f, "content_type", "") or "application/octet-stream",
                uploaded_by=request.user,
            )
            entry = {
                "id": str(att.id),
                "name": att.name,
                "title": title or att.name,
                "description": description,
                "url": att.file.url,
                "size": att.size,
                "uploaded_by": request.user.get_full_name() or request.user.email,
                "uploaded_at": att.created_at.isoformat(),
            }
            existing.append(entry)
            AuditLog.objects.create(
                user=request.user,
                action="task.attachment.added",
                model_name="Task",
                object_id=task.id,
                changes={"attachment_id": entry["id"], "title": entry["title"], "name": entry["name"]},
            )
        task.attachments = existing
        task.save(update_fields=["attachments", "updated_at"])
        return Response(self.get_serializer(task).data)

    @action(detail=True, methods=["patch"], url_path=r"attachments/(?P<attachment_id>[^/]+)")
    def update_attachment(self, request, pk=None, attachment_id=None):
        """Edit title/description of an existing attachment entry."""
        from apps.core.models import AuditLog

        task = self.get_object()
        if not self._can_modify_task(request.user, task):
            return Response({"error": "forbidden"}, status=403)
        entries = list(task.attachments or [])
        target = next((a for a in entries if str(a.get("id")) == str(attachment_id)), None)
        if target is None:
            return Response({"error": "attachment not found"}, status=404)

        before = {"title": target.get("title"), "description": target.get("description")}
        if "title" in request.data:
            target["title"] = (request.data.get("title") or "").strip() or target.get("name") or ""
        if "description" in request.data:
            target["description"] = (request.data.get("description") or "").strip()
        after = {"title": target.get("title"), "description": target.get("description")}

        task.attachments = entries
        task.save(update_fields=["attachments", "updated_at"])
        AuditLog.objects.create(
            user=request.user,
            action="task.attachment.updated",
            model_name="Task",
            object_id=task.id,
            changes={"attachment_id": str(attachment_id), "before": before, "after": after},
        )
        return Response(self.get_serializer(task).data)

    @action(detail=True, methods=["delete"], url_path=r"attachments/(?P<attachment_id>[^/]+)")
    def remove_attachment(self, request, pk=None, attachment_id=None):
        """Remove attachment from task.attachments by id."""
        from apps.core.models import AuditLog

        task = self.get_object()
        if not self._can_modify_task(request.user, task):
            return Response({"error": "forbidden"}, status=403)
        before = list(task.attachments or [])
        target = next((a for a in before if str(a.get("id")) == str(attachment_id)), None)
        if target is None:
            return Response({"error": "attachment not found"}, status=404)
        after = [a for a in before if str(a.get("id")) != str(attachment_id)]
        task.attachments = after
        task.save(update_fields=["attachments", "updated_at"])
        AuditLog.objects.create(
            user=request.user,
            action="task.attachment.removed",
            model_name="Task",
            object_id=task.id,
            changes={"attachment_id": str(attachment_id), "name": target.get("name"), "title": target.get("title")},
        )
        return Response(self.get_serializer(task).data)

    @action(detail=True, methods=["get"], url_path="activity")
    def activity(self, request, pk=None):
        """Combined activity feed: AuditLog entries + creation event."""
        from apps.core.models import AuditLog

        task = self.get_object()
        logs = AuditLog.objects.filter(model_name="Task", object_id=task.id).select_related("user").order_by("-created_at")
        items = []
        for log in logs:
            items.append({
                "id": str(log.id),
                "action": log.action,
                "actor": log.user.get_full_name() if log.user else None,
                "actor_email": log.user.email if log.user else None,
                "changes": log.changes,
                "created_at": log.created_at.isoformat(),
            })
        # Always include creation event as final tail
        creator_name = (task.created_by.get_full_name() or task.created_by.email) if task.created_by_id else "System"
        items.append({
            "id": f"task-create-{task.id}",
            "action": "task.created",
            "actor": creator_name,
            "actor_email": task.created_by.email if task.created_by_id else None,
            "changes": {"title": task.title},
            "created_at": task.created_at.isoformat(),
        })
        return Response(items)

    @action(detail=True, methods=["post"], url_path="approve")
    def approve(self, request, pk=None):
        """Creator-only: approve the assignee's completed work. Stamps approved_at (starts the 10-day archive clock) and marks done."""
        from django.utils import timezone
        from apps.core.models import AuditLog

        task = self.get_object()
        is_admin = request.user.is_staff or request.user.is_superuser
        if task.created_by_id != request.user.id and not is_admin:
            return Response({"error": "only the task creator may approve"}, status=403)
        task.approved_at = timezone.now()
        task.status = "done"
        task.save(update_fields=["approved_at", "status", "updated_at"])
        AuditLog.objects.create(
            user=request.user, action="task.approved", model_name="Task", object_id=task.id,
            changes={"approved_at": task.approved_at.isoformat()},
        )
        # Notify the current assignee that their work was approved.
        if task.assignee_id and task.assignee_id != request.user.id:
            from apps.notifications.handler import NotificationHandler
            approver_name = request.user.get_full_name() or request.user.email
            NotificationHandler.send(
                user=task.assignee,
                title="Task approved",
                message=f"{approver_name} approved your work on “{task.title}”.",
                notification_type="task",
                priority="medium",
                icon="check-square",
                link=f"/tasks/{task.id}",
                related_user=request.user,
                broadcast=True,
            )
        return Response(self.get_serializer(task).data)

    @action(detail=True, methods=["post"], url_path="archive")
    def archive(self, request, pk=None):
        """Manually archive a task before the auto-archive window."""
        from django.utils import timezone
        from apps.core.models import AuditLog

        task = self.get_object()
        if not self._can_modify_task(request.user, task):
            return Response({"error": "forbidden"}, status=403)
        task.status = "archived"
        task.archived_at = timezone.now()
        task.save(update_fields=["status", "archived_at", "updated_at"])
        AuditLog.objects.create(
            user=request.user, action="task.archived", model_name="Task", object_id=task.id,
            changes={"manual": True},
        )
        return Response(self.get_serializer(task).data)

    @action(detail=True, methods=["post"], url_path="restore")
    def restore(self, request, pk=None):
        """Restore an archived task. Returns to 'done' if it was approved, else 'todo'."""
        from apps.core.models import AuditLog

        task = self.get_object()
        if not self._can_modify_task(request.user, task):
            return Response({"error": "forbidden"}, status=403)
        if task.status != "archived":
            return Response({"error": "task is not archived"}, status=400)
        task.status = "done" if task.approved_at else "todo"
        task.archived_at = None
        task.save(update_fields=["status", "archived_at", "updated_at"])
        AuditLog.objects.create(
            user=request.user, action="task.restored", model_name="Task", object_id=task.id,
            changes={"status": task.status},
        )
        return Response(self.get_serializer(task).data)

    @extend_schema(
        parameters=[
            OpenApiParameter(name="status", type=str, description="Filter by status"),
            OpenApiParameter(name="priority", type=str, description="Filter by priority"),
            OpenApiParameter(name="assignee", type=str, description="Filter by assignee ID"),
            OpenApiParameter(name="project", type=str, description="Filter by project ID"),
            OpenApiParameter(name="event", type=str, description="Filter by event ID"),
            OpenApiParameter(name="source", type=str, description="Filter by origin (e.g. 'helpdesk')"),
        ]
    )
    def list(self, request, *args, **kwargs):
        self._auto_archive_eligible()
        queryset = self.get_queryset()
        event = request.query_params.get("event")
        if event:
            queryset = queryset.filter(event_id=event)
        source = request.query_params.get("source")
        if source:
            queryset = queryset.filter(source=source)
        status = request.query_params.get("status")
        if status:
            queryset = queryset.filter(status=status)
        else:
            # Archived tasks are hidden from normal lists; request ?status=archived to see them.
            queryset = queryset.exclude(status="archived")
        priority = request.query_params.get("priority")
        if priority:
            queryset = queryset.filter(priority=priority)
        assignee = request.query_params.get("assignee")
        if assignee:
            queryset = queryset.filter(assignee_id=assignee)
        project = request.query_params.get("project")
        if project:
            queryset = queryset.filter(project_id=project)
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)


class MessageViewSet(viewsets.ModelViewSet):
    """ViewSet for Message management."""
    rbac_domain = "tasks"
    queryset = Message.objects.all().select_related("sender").order_by("created_at")
    serializer_class = MessageSerializer
    parser_classes = [JSONParser, MultiPartParser, FormParser]

    @extend_schema(
        parameters=[
            OpenApiParameter(name="related_task", type=str, description="Filter by task ID"),
            OpenApiParameter(name="related_approval", type=str, description="Filter by approval ID"),
        ]
    )
    def list(self, request, *args, **kwargs):
        qs = self.get_queryset()
        related_task = request.query_params.get("related_task")
        related_approval = request.query_params.get("related_approval")
        if related_task:
            qs = qs.filter(related_task_id=related_task)
        if related_approval:
            qs = qs.filter(related_approval_id=related_approval)
        serializer = self.get_serializer(qs, many=True)
        return Response(serializer.data)

    def create(self, request, *args, **kwargs):
        from apps.core.models import Attachment, AuditLog

        if not request.user.is_authenticated:
            return Response({"error": "authentication required"}, status=401)

        content = request.data.get("content", "").strip()
        related_task = request.data.get("related_task") or None
        related_approval = request.data.get("related_approval") or None
        if not content and not (request.FILES.getlist("files") or request.FILES.getlist("file")):
            return Response({"error": "content or file required"}, status=400)

        # Persist any uploaded files via Attachment, collect URLs
        from apps.core.uploads import validate_upload
        attachments = []
        files = request.FILES.getlist("files") or request.FILES.getlist("file")
        for f in files:
            validate_upload(f)
        for f in files:
            att = Attachment.objects.create(
                file=f,
                name=f.name,
                size=f.size,
                mime_type=getattr(f, "content_type", "") or "application/octet-stream",
                uploaded_by=request.user,
            )
            attachments.append({"id": str(att.id), "name": att.name, "url": att.file.url, "size": att.size})

        msg = Message.objects.create(
            sender=request.user,
            content=content,
            attachments=attachments,
            related_task_id=related_task,
            related_approval_id=related_approval,
        )
        if related_task:
            AuditLog.objects.create(
                user=request.user,
                action="task.message.created",
                model_name="Task",
                object_id=related_task,
                changes={
                    "message_id": str(msg.id),
                    "preview": content[:120],
                    "attachment_count": len(attachments),
                },
            )
        return Response(self.get_serializer(msg).data, status=201)


class ApprovalViewSet(viewsets.ModelViewSet):
    """ViewSet for Approval management. Scoped: requester or approver only."""
    rbac_domain = "tasks"
    rbac_action_map = {"decide": "approve"}
    serializer_class = ApprovalSerializer

    def get_queryset(self):
        from django.db.models import Q
        qs = Approval.objects.select_related("requester", "approver")
        user = self.request.user
        if not user.is_authenticated:
            return qs.none()
        if user.is_staff or user.is_superuser:
            return qs
        return qs.filter(Q(requester=user) | Q(approver=user))

    def perform_create(self, serializer):
        # requester is always the caller — read_only_fields prevents client override
        serializer.save(requester=self.request.user, status="pending")

    @action(detail=True, methods=["post"], url_path="decide")
    def decide(self, request, pk=None):
        """Approver-only: set status to approved/rejected/revised + record resolution timestamp."""
        from django.utils import timezone
        approval = self.get_object()
        if approval.approver_id != request.user.id and not (request.user.is_staff or request.user.is_superuser):
            return Response({"error": "only the assigned approver may decide"}, status=403)
        decision = request.data.get("status")
        if decision not in ("approved", "rejected", "revised"):
            return Response({"error": "status must be approved|rejected|revised"}, status=400)
        approval.status = decision
        approval.comment = (request.data.get("comment") or "").strip()
        approval.resolved_at = timezone.now()
        approval.save(update_fields=["status", "comment", "resolved_at"])
        return Response(self.get_serializer(approval).data)

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