import swapper

from django.db import transaction
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema
from dynamicforms import fields
from dynamicforms.action import Actions, TableAction, TablePosition
from dynamicforms.mixins import DisplayMode
from dynamicforms.serializers import ModelSerializer
from dynamicforms.viewsets import ModelViewSet
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request

from django_project_base.base.event import UserInviteFoundEvent
from django_project_base.base.exceptions import InviteActionNotImplementedException
from django_project_base.base.viewsets import ProjectFilteringViewSet
from django_project_base.constants import INVITE_NOTIFICATION_TEXT
from django_project_base.notifications.email_notification import EMailNotificationWithListOfEmails
from django_project_base.notifications.models import DjangoProjectBaseMessage
from django_project_base.notifications.rest.notification import MessageBodyField
from django_project_base.utils import get_host_url, get_pk_name


class AcceptedField(fields.BooleanField):
    def __init__(self, **kw):
        super().__init__(**kw)
        self.read_only = True

    def to_representation(self, value, row_data=None):
        if row_data and row_data.accepted:
            return True
        return False


class InviteTextField(MessageBodyField):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.label = _("Invitation text")
        self.help_text = _(
            "Use %LINK% as a placeholder for invite link. If invite text is empty, value from settings will be used"
        )
        self.allow_blank = True


class ProjectUserInviteSerializer(ModelSerializer):
    template_context = dict(url_reverse="project-user-invite")
    form_titles = {
        "table": _("Project invites"),
        "new": _("Inviting project member"),
        "edit": "",
    }

    actions = Actions(
        TableAction(TablePosition.HEADER, _("Add"), title=_("Add new record"), name="add", icon="add-circle-outline"),
        add_form_buttons=True,
        add_default_filter=True,
        add_default_crud=False,
    )

    id = fields.AutoGeneratedField(display=DisplayMode.HIDDEN)
    invited_by = fields.AutoGeneratedField(display=DisplayMode.HIDDEN)
    project = fields.PrimaryKeyRelatedField(
        display=DisplayMode.SUPPRESS,
        queryset=swapper.load_model("django_project_base", "Project").objects.all(),
    )
    role = fields.PrimaryKeyRelatedField(
        queryset=swapper.load_model("django_project_base", "Role").objects.all(),
        allow_null=True,
    )
    accepted = AcceptedField(display_form=DisplayMode.HIDDEN)
    text = InviteTextField()

    class Meta:
        model = swapper.load_model("django_project_base", "Invite")
        exclude = ()
        fields = (
            get_pk_name(swapper.load_model("django_project_base", "Invite")),
            "email",
            "role",
            "accepted",
            "text",
            "project",
            "invited_by",
        )


class ProjectUserInviteViewSet(ProjectFilteringViewSet):
    # TODO: ADD INVITE SHOULD BE PERMISSION BASED ON ROLE
    permission_classes = (IsAuthenticated,)
    serializer_class = ProjectUserInviteSerializer
    model = staticmethod(lambda: swapper.load_model("django_project_base", "Invite"))

    def get_permissions(self):
        if self.action == "accept":
            return [IsAuthenticated()]
        else:
            return super().get_permissions()

    def new_object(self: ModelViewSet):
        new_object = super().new_object()
        new_object.project = self.request.selected_project
        return new_object

    @transaction.atomic
    def update(self, request, *args, **kwargs):
        self.request.data["project"] = self.request.selected_project.pk
        self.request.data["invited_by"] = self.request.user.userprofile
        created = super().create(request, *args, **kwargs)
        invite_url = (
            f"{get_host_url(request)}account/project-user-invite/"
            f"{created.data[get_pk_name(self.get_serializer_class().Meta.model)]}/accept"
        )
        invite_text = request.data.get("text")
        if not invite_text or "%LINK%" not in invite_text:
            invite_text = (
                swapper.load_model("django_project_base", "ProjectSettings")
                .objects.get(project=self.request.selected_project, name=INVITE_NOTIFICATION_TEXT)
                .python_value
            )
            invite_text = invite_text.replace("__LINK__", invite_url)
        else:
            invite_text = invite_text.replace("%LINK%", invite_url)

        EMailNotificationWithListOfEmails(
            message=DjangoProjectBaseMessage(
                subject=_("You are invited to project") + f" {self.request.selected_project.name}",
                body=invite_text,
                footer="",
                content_type=DjangoProjectBaseMessage.HTML,
            ),
            recipients=[self.request.data["email"]],
            project=self.request.selected_project.slug,
            user=self.request.user.pk,
        ).send()
        return created

    @transaction.atomic
    def create(self, request, *args, **kwargs):
        raise InviteActionNotImplementedException

    @transaction.atomic
    def partial_update(self, request, *args, **kwargs):
        raise InviteActionNotImplementedException

    @transaction.atomic
    def destroy(self, request, *args, **kwargs):
        raise InviteActionNotImplementedException

    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(exclude=True)
    @action(methods=["GET"], detail=True, url_name="accept", url_path="accept")
    def accept(self, request: Request, pk: str, *args, **kwargs) -> HttpResponse:
        invite = get_object_or_404(swapper.load_model("django_project_base", "Invite"), pk=pk)
        response = HttpResponseRedirect(get_host_url(request))
        if not invite.accepted:
            if request.user.is_authenticated:
                UserInviteFoundEvent(request.user).trigger(payload=invite, request=request)
                return response
            response.set_cookie("invite-pk", pk)
            request.session["invite-pk"] = pk
        return response
