# Python imports
import json

# Django imports
from django.utils import timezone
from django.db.models import (
    Prefetch,
    OuterRef,
    Func,
    F,
    Q,
    Count,
    Case,
    Value,
    CharField,
    When,
    Exists,
    Max,
    IntegerField,
)
from django.core.serializers.json import DjangoJSONEncoder

# Third Party imports
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny, IsAuthenticated

# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.app.serializers import (
    IssueCommentSerializer,
    IssueReactionSerializer,
    CommentReactionSerializer,
    IssueVoteSerializer,
    IssuePublicSerializer,
)

from plane.db.models import (
    Issue,
    IssueComment,
    Label,
    IssueLink,
    IssueAttachment,
    State,
    ProjectMember,
    IssueReaction,
    CommentReaction,
    ProjectDeployBoard,
    IssueVote,
    ProjectPublicMember,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results
from plane.utils.issue_filters import issue_filters


class IssueCommentPublicViewSet(BaseViewSet):
    serializer_class = IssueCommentSerializer
    model = IssueComment

    filterset_fields = [
        "issue__id",
        "workspace__id",
    ]

    def get_permissions(self):
        if self.action in ["list", "retrieve"]:
            self.permission_classes = [
                AllowAny,
            ]
        else:
            self.permission_classes = [
                IsAuthenticated,
            ]

        return super(IssueCommentPublicViewSet, self).get_permissions()

    def get_queryset(self):
        try:
            project_deploy_board = ProjectDeployBoard.objects.get(
                workspace__slug=self.kwargs.get("slug"),
                project_id=self.kwargs.get("project_id"),
            )
            if project_deploy_board.comments:
                return self.filter_queryset(
                    super()
                    .get_queryset()
                    .filter(workspace__slug=self.kwargs.get("slug"))
                    .filter(issue_id=self.kwargs.get("issue_id"))
                    .filter(access="EXTERNAL")
                    .select_related("project")
                    .select_related("workspace")
                    .select_related("issue")
                    .annotate(
                        is_member=Exists(
                            ProjectMember.objects.filter(
                                workspace__slug=self.kwargs.get("slug"),
                                project_id=self.kwargs.get("project_id"),
                                member_id=self.request.user.id,
                                is_active=True,
                            )
                        )
                    )
                    .distinct()
                ).order_by("created_at")
            return IssueComment.objects.none()
        except ProjectDeployBoard.DoesNotExist:
            return IssueComment.objects.none()

    def create(self, request, slug, project_id, issue_id):
        project_deploy_board = ProjectDeployBoard.objects.get(
            workspace__slug=slug, project_id=project_id
        )

        if not project_deploy_board.comments:
            return Response(
                {"error": "Comments are not enabled for this project"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        serializer = IssueCommentSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(
                project_id=project_id,
                issue_id=issue_id,
                actor=request.user,
                access="EXTERNAL",
            )
            issue_activity.delay(
                type="comment.activity.created",
                requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
                actor_id=str(request.user.id),
                issue_id=str(issue_id),
                project_id=str(project_id),
                current_instance=None,
                epoch=int(timezone.now().timestamp()),
            )
            if not ProjectMember.objects.filter(
                project_id=project_id,
                member=request.user,
                is_active=True,
            ).exists():
                # Add the user for workspace tracking
                _ = ProjectPublicMember.objects.get_or_create(
                    project_id=project_id,
                    member=request.user,
                )

            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def partial_update(self, request, slug, project_id, issue_id, pk):
        project_deploy_board = ProjectDeployBoard.objects.get(
            workspace__slug=slug, project_id=project_id
        )

        if not project_deploy_board.comments:
            return Response(
                {"error": "Comments are not enabled for this project"},
                status=status.HTTP_400_BAD_REQUEST,
            )
        comment = IssueComment.objects.get(
            workspace__slug=slug, pk=pk, actor=request.user
        )
        serializer = IssueCommentSerializer(comment, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            issue_activity.delay(
                type="comment.activity.updated",
                requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
                actor_id=str(request.user.id),
                issue_id=str(issue_id),
                project_id=str(project_id),
                current_instance=json.dumps(
                    IssueCommentSerializer(comment).data,
                    cls=DjangoJSONEncoder,
                ),
                epoch=int(timezone.now().timestamp()),
            )
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, slug, project_id, issue_id, pk):
        project_deploy_board = ProjectDeployBoard.objects.get(
            workspace__slug=slug, project_id=project_id
        )

        if not project_deploy_board.comments:
            return Response(
                {"error": "Comments are not enabled for this project"},
                status=status.HTTP_400_BAD_REQUEST,
            )
        comment = IssueComment.objects.get(
            workspace__slug=slug, pk=pk, project_id=project_id, actor=request.user
        )
        issue_activity.delay(
            type="comment.activity.deleted",
            requested_data=json.dumps({"comment_id": str(pk)}),
            actor_id=str(request.user.id),
            issue_id=str(issue_id),
            project_id=str(project_id),
            current_instance=json.dumps(
                IssueCommentSerializer(comment).data,
                cls=DjangoJSONEncoder,
            ),
            epoch=int(timezone.now().timestamp()),
        )
        comment.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


class IssueReactionPublicViewSet(BaseViewSet):
    serializer_class = IssueReactionSerializer
    model = IssueReaction

    def get_queryset(self):
        try:
            project_deploy_board = ProjectDeployBoard.objects.get(
                workspace__slug=self.kwargs.get("slug"),
                project_id=self.kwargs.get("project_id"),
            )
            if project_deploy_board.reactions:
                return (
                    super()
                    .get_queryset()
                    .filter(workspace__slug=self.kwargs.get("slug"))
                    .filter(project_id=self.kwargs.get("project_id"))
                    .filter(issue_id=self.kwargs.get("issue_id"))
                    .order_by("-created_at")
                    .distinct()
                )
            return IssueReaction.objects.none()
        except ProjectDeployBoard.DoesNotExist:
            return IssueReaction.objects.none()

    def create(self, request, slug, project_id, issue_id):
        project_deploy_board = ProjectDeployBoard.objects.get(
            workspace__slug=slug, project_id=project_id
        )

        if not project_deploy_board.reactions:
            return Response(
                {"error": "Reactions are not enabled for this project board"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        serializer = IssueReactionSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(
                project_id=project_id, issue_id=issue_id, actor=request.user
            )
            if not ProjectMember.objects.filter(
                project_id=project_id,
                member=request.user,
                is_active=True,
            ).exists():
                # Add the user for workspace tracking
                _ = ProjectPublicMember.objects.get_or_create(
                    project_id=project_id,
                    member=request.user,
                )
            issue_activity.delay(
                type="issue_reaction.activity.created",
                requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
                actor_id=str(self.request.user.id),
                issue_id=str(self.kwargs.get("issue_id", None)),
                project_id=str(self.kwargs.get("project_id", None)),
                current_instance=None,
                epoch=int(timezone.now().timestamp()),
            )
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, slug, project_id, issue_id, reaction_code):
        project_deploy_board = ProjectDeployBoard.objects.get(
            workspace__slug=slug, project_id=project_id
        )

        if not project_deploy_board.reactions:
            return Response(
                {"error": "Reactions are not enabled for this project board"},
                status=status.HTTP_400_BAD_REQUEST,
            )
        issue_reaction = IssueReaction.objects.get(
            workspace__slug=slug,
            issue_id=issue_id,
            reaction=reaction_code,
            actor=request.user,
        )
        issue_activity.delay(
            type="issue_reaction.activity.deleted",
            requested_data=None,
            actor_id=str(self.request.user.id),
            issue_id=str(self.kwargs.get("issue_id", None)),
            project_id=str(self.kwargs.get("project_id", None)),
            current_instance=json.dumps(
                {
                    "reaction": str(reaction_code),
                    "identifier": str(issue_reaction.id),
                }
            ),
            epoch=int(timezone.now().timestamp()),
        )
        issue_reaction.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


class CommentReactionPublicViewSet(BaseViewSet):
    serializer_class = CommentReactionSerializer
    model = CommentReaction

    def get_queryset(self):
        try:
            project_deploy_board = ProjectDeployBoard.objects.get(
                workspace__slug=self.kwargs.get("slug"),
                project_id=self.kwargs.get("project_id"),
            )
            if project_deploy_board.reactions:
                return (
                    super()
                    .get_queryset()
                    .filter(workspace__slug=self.kwargs.get("slug"))
                    .filter(project_id=self.kwargs.get("project_id"))
                    .filter(comment_id=self.kwargs.get("comment_id"))
                    .order_by("-created_at")
                    .distinct()
                )
            return CommentReaction.objects.none()
        except ProjectDeployBoard.DoesNotExist:
            return CommentReaction.objects.none()

    def create(self, request, slug, project_id, comment_id):
        project_deploy_board = ProjectDeployBoard.objects.get(
            workspace__slug=slug, project_id=project_id
        )

        if not project_deploy_board.reactions:
            return Response(
                {"error": "Reactions are not enabled for this board"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        serializer = CommentReactionSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(
                project_id=project_id, comment_id=comment_id, actor=request.user
            )
            if not ProjectMember.objects.filter(
                project_id=project_id,
                member=request.user,
                is_active=True,
            ).exists():
                # Add the user for workspace tracking
                _ = ProjectPublicMember.objects.get_or_create(
                    project_id=project_id,
                    member=request.user,
                )
            issue_activity.delay(
                type="comment_reaction.activity.created",
                requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
                actor_id=str(self.request.user.id),
                issue_id=None,
                project_id=str(self.kwargs.get("project_id", None)),
                current_instance=None,
                epoch=int(timezone.now().timestamp()),
            )
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, slug, project_id, comment_id, reaction_code):
        project_deploy_board = ProjectDeployBoard.objects.get(
            workspace__slug=slug, project_id=project_id
        )
        if not project_deploy_board.reactions:
            return Response(
                {"error": "Reactions are not enabled for this board"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        comment_reaction = CommentReaction.objects.get(
            project_id=project_id,
            workspace__slug=slug,
            comment_id=comment_id,
            reaction=reaction_code,
            actor=request.user,
        )
        issue_activity.delay(
            type="comment_reaction.activity.deleted",
            requested_data=None,
            actor_id=str(self.request.user.id),
            issue_id=None,
            project_id=str(self.kwargs.get("project_id", None)),
            current_instance=json.dumps(
                {
                    "reaction": str(reaction_code),
                    "identifier": str(comment_reaction.id),
                    "comment_id": str(comment_id),
                }
            ),
            epoch=int(timezone.now().timestamp()),
        )
        comment_reaction.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


class IssueVotePublicViewSet(BaseViewSet):
    model = IssueVote
    serializer_class = IssueVoteSerializer

    def get_queryset(self):
        try:
            project_deploy_board = ProjectDeployBoard.objects.get(
                workspace__slug=self.kwargs.get("slug"),
                project_id=self.kwargs.get("project_id"),
            )
            if project_deploy_board.votes:
                return (
                    super()
                    .get_queryset()
                    .filter(issue_id=self.kwargs.get("issue_id"))
                    .filter(workspace__slug=self.kwargs.get("slug"))
                    .filter(project_id=self.kwargs.get("project_id"))
                )
            return IssueVote.objects.none()
        except ProjectDeployBoard.DoesNotExist:
            return IssueVote.objects.none()

    def create(self, request, slug, project_id, issue_id):
        issue_vote, _ = IssueVote.objects.get_or_create(
            actor_id=request.user.id,
            project_id=project_id,
            issue_id=issue_id,
        )
        # Add the user for workspace tracking
        if not ProjectMember.objects.filter(
            project_id=project_id,
            member=request.user,
            is_active=True,
        ).exists():
            _ = ProjectPublicMember.objects.get_or_create(
                project_id=project_id,
                member=request.user,
            )
        issue_vote.vote = request.data.get("vote", 1)
        issue_vote.save()
        issue_activity.delay(
            type="issue_vote.activity.created",
            requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
            actor_id=str(self.request.user.id),
            issue_id=str(self.kwargs.get("issue_id", None)),
            project_id=str(self.kwargs.get("project_id", None)),
            current_instance=None,
            epoch=int(timezone.now().timestamp()),
        )
        serializer = IssueVoteSerializer(issue_vote)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    def destroy(self, request, slug, project_id, issue_id):
        issue_vote = IssueVote.objects.get(
            workspace__slug=slug,
            project_id=project_id,
            issue_id=issue_id,
            actor_id=request.user.id,
        )
        issue_activity.delay(
            type="issue_vote.activity.deleted",
            requested_data=None,
            actor_id=str(self.request.user.id),
            issue_id=str(self.kwargs.get("issue_id", None)),
            project_id=str(self.kwargs.get("project_id", None)),
            current_instance=json.dumps(
                {
                    "vote": str(issue_vote.vote),
                    "identifier": str(issue_vote.id),
                }
            ),
            epoch=int(timezone.now().timestamp()),
        )
        issue_vote.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


class IssueRetrievePublicEndpoint(BaseAPIView):
    permission_classes = [
        AllowAny,
    ]

    def get(self, request, slug, project_id, issue_id):
        issue = Issue.objects.get(
            workspace__slug=slug, project_id=project_id, pk=issue_id
        )
        serializer = IssuePublicSerializer(issue)
        return Response(serializer.data, status=status.HTTP_200_OK)


class ProjectIssuesPublicEndpoint(BaseAPIView):
    permission_classes = [
        AllowAny,
    ]

    def get(self, request, slug, project_id):
        project_deploy_board = ProjectDeployBoard.objects.get(
            workspace__slug=slug, project_id=project_id
        )

        filters = issue_filters(request.query_params, "GET")

        # Custom ordering for priority and state
        priority_order = ["urgent", "high", "medium", "low", "none"]
        state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]

        order_by_param = request.GET.get("order_by", "-created_at")

        issue_queryset = (
            Issue.issue_objects.annotate(
                sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
                .order_by()
                .annotate(count=Func(F("id"), function="Count"))
                .values("count")
            )
            .filter(project_id=project_id)
            .filter(workspace__slug=slug)
            .select_related("project", "workspace", "state", "parent")
            .prefetch_related("assignees", "labels")
            .prefetch_related(
                Prefetch(
                    "issue_reactions",
                    queryset=IssueReaction.objects.select_related("actor"),
                )
            )
            .prefetch_related(
                Prefetch(
                    "votes",
                    queryset=IssueVote.objects.select_related("actor"),
                )
            )
            .filter(**filters)
            .annotate(cycle_id=F("issue_cycle__cycle_id"))
            .annotate(module_id=F("issue_module__module_id"))
            .annotate(
                link_count=IssueLink.objects.filter(issue=OuterRef("id"))
                .order_by()
                .annotate(count=Func(F("id"), function="Count"))
                .values("count")
            )
            .annotate(
                attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
                .order_by()
                .annotate(count=Func(F("id"), function="Count"))
                .values("count")
            )
        )

        # Priority Ordering
        if order_by_param == "priority" or order_by_param == "-priority":
            priority_order = (
                priority_order if order_by_param == "priority" else priority_order[::-1]
            )
            issue_queryset = issue_queryset.annotate(
                priority_order=Case(
                    *[
                        When(priority=p, then=Value(i))
                        for i, p in enumerate(priority_order)
                    ],
                    output_field=CharField(),
                )
            ).order_by("priority_order")

        # State Ordering
        elif order_by_param in [
            "state__name",
            "state__group",
            "-state__name",
            "-state__group",
        ]:
            state_order = (
                state_order
                if order_by_param in ["state__name", "state__group"]
                else state_order[::-1]
            )
            issue_queryset = issue_queryset.annotate(
                state_order=Case(
                    *[
                        When(state__group=state_group, then=Value(i))
                        for i, state_group in enumerate(state_order)
                    ],
                    default=Value(len(state_order)),
                    output_field=CharField(),
                )
            ).order_by("state_order")
        # assignee and label ordering
        elif order_by_param in [
            "labels__name",
            "-labels__name",
            "assignees__first_name",
            "-assignees__first_name",
        ]:
            issue_queryset = issue_queryset.annotate(
                max_values=Max(
                    order_by_param[1::]
                    if order_by_param.startswith("-")
                    else order_by_param
                )
            ).order_by(
                "-max_values" if order_by_param.startswith("-") else "max_values"
            )
        else:
            issue_queryset = issue_queryset.order_by(order_by_param)

        issues = IssuePublicSerializer(issue_queryset, many=True).data

        state_group_order = [
            "backlog",
            "unstarted",
            "started",
            "completed",
            "cancelled",
        ]

        states = (
            State.objects.filter(
                ~Q(name="Triage"),
                workspace__slug=slug,
                project_id=project_id,
            )
            .annotate(
                custom_order=Case(
                    *[
                        When(group=value, then=Value(index))
                        for index, value in enumerate(state_group_order)
                    ],
                    default=Value(len(state_group_order)),
                    output_field=IntegerField(),
                ),
            )
            .values("name", "group", "color", "id")
            .order_by("custom_order", "sequence")
        )

        labels = Label.objects.filter(
            workspace__slug=slug, project_id=project_id
        ).values("id", "name", "color", "parent")

        ## Grouping the results
        group_by = request.GET.get("group_by", False)
        if group_by:
            issues = group_results(issues, group_by)

        return Response(
            {
                "issues": issues,
                "states": states,
                "labels": labels,
            },
            status=status.HTTP_200_OK,
        )