from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DeleteView, DetailView, TemplateView
from django_scopes import scopes_disabled
from rules.contrib.views import AutoPermissionRequiredMixin

from servala.core.models import (
    BillingEntity,
    Organization,
    OrganizationInvitation,
    OrganizationMembership,
    ServiceInstance,
)
from servala.frontend.forms.organization import (
    OrganizationCreateForm,
    OrganizationForm,
    OrganizationInvitationForm,
)
from servala.frontend.views.mixins import (
    HtmxUpdateView,
    HtmxViewMixin,
    OrganizationViewMixin,
)


class OrganizationCreateView(AutoPermissionRequiredMixin, CreateView):
    form_class = OrganizationCreateForm
    model = Organization
    template_name = "frontend/organizations/create.html"

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["user"] = self.request.user
        return kwargs

    def form_valid(self, form):
        billing_choice = form.cleaned_data.get("billing_processing_choice")
        billing_entity = None
        name = form.cleaned_data["name"]

        if not billing_choice or billing_choice == "new":
            billing_entity = BillingEntity.create_from_data(
                name,
                {
                    key: value
                    for key, value in form.cleaned_data.items()
                    if key.startswith("invoice_")
                },
            )
        elif odoo_id := form.cleaned_data.get("existing_odoo_address_id"):
            billing_entity = BillingEntity.objects.filter(
                odoo_invoice_id=odoo_id
            ).first()

            if not billing_entity:
                billing_entity = BillingEntity.create_from_id(name, odoo_id)

        if not billing_entity:
            form.add_error(
                None,
                _(
                    "Could not determine or create the billing entity. Please check your input."
                ),
            )
            return self.form_invalid(form)

        form.instance.billing_entity = billing_entity
        instance = form.instance.create_organization(
            form.instance, owner=self.request.user
        )
        return redirect(instance.urls.base)


class OrganizationDashboardView(
    AutoPermissionRequiredMixin, OrganizationViewMixin, DetailView
):
    template_name = "frontend/organizations/dashboard.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        organization = self.get_object()

        service_instances = ServiceInstance.objects.filter(organization=organization)
        recent_instances = service_instances.order_by("-created_at")[:5]

        for instance in recent_instances:
            instance.has_change_permission = self.request.user.has_perm(
                "core.change_serviceinstance", instance
            )

        user_membership = OrganizationMembership.objects.filter(
            user=self.request.user, organization=organization
        ).first()
        user_role = user_membership.role if user_membership else None

        context.update(
            {
                "service_instances_count": service_instances.count(),
                "service_instances": recent_instances,
                "members_count": organization.members.count(),
                "has_instances": service_instances.exists(),
                "user_role": user_role,
            }
        )

        return context


class OrganizationMembershipMixin:
    template_name = "frontend/organizations/update.html"

    @cached_property
    def user_role(self):
        membership = (
            OrganizationMembership.objects.filter(
                user=self.request.user, organization=self.get_object()
            )
            .order_by("role")
            .first()
        )
        return membership.role if membership else None

    @cached_property
    def can_manage_members(self):
        return self.request.user.has_perm(
            "core.change_organization", self.request.organization
        )

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        organization = self.get_object()

        if self.can_manage_members:
            memberships = (
                OrganizationMembership.objects.filter(organization=organization)
                .select_related("user")
                .order_by("role", "user__email")
            )
            pending_invitations = OrganizationInvitation.objects.filter(
                organization=organization, accepted_by__isnull=True
            ).order_by("-created_at")
            invitation_form = OrganizationInvitationForm(
                organization=organization, user_role=self.user_role
            )
            context.update(
                {
                    "memberships": memberships,
                    "pending_invitations": pending_invitations,
                    "invitation_form": invitation_form,
                    "can_manage_members": self.can_manage_members,
                    "user_role": self.user_role,
                }
            )

        return context


class OrganizationUpdateView(
    OrganizationViewMixin, OrganizationMembershipMixin, HtmxUpdateView
):
    form_class = OrganizationForm
    fragments = (
        "org-name",
        "org-name-edit",
        "members-list",
        "pending-invitations-card",
    )

    def post(self, request, *args, **kwargs):
        if "invite_email" in request.POST:
            return self.handle_invitation(request)
        return super().post(request, *args, **kwargs)

    def handle_invitation(self, request):
        organization = self.get_object()
        if not self.can_manage_members:
            messages.error(request, _("You do not have permission to invite members."))
            return redirect(self.get_success_url())

        form = OrganizationInvitationForm(
            request.POST, organization=organization, user_role=self.user_role
        )

        if form.is_valid():
            invitation = form.save(commit=False)
            invitation.created_by = request.user
            invitation.save()

            try:
                invitation.send_invitation_email(request)
                messages.success(
                    request,
                    _(
                        "Invitation sent to {email}. They will receive an email with the invitation link."
                    ).format(email=invitation.email),
                )
            except Exception:
                messages.warning(
                    request,
                    _(
                        "Invitation created for {email}, but email failed to send. Share this link manually: {url}"
                    ).format(
                        email=invitation.email,
                        url=request.build_absolute_uri(invitation.urls.accept),
                    ),
                )
        else:
            for error in form.errors.values():
                for error_msg in error:
                    messages.error(request, error_msg)

        if self.is_htmx and self._get_fragment():
            return self.get(request, *self.args, **self.kwargs)

        return redirect(self.get_success_url())

    def get_success_url(self):
        return self.request.path


@method_decorator(scopes_disabled(), name="dispatch")
class InvitationAcceptView(TemplateView):
    template_name = "frontend/organizations/invitation_accept.html"

    def get_invitation(self):
        secret = self.kwargs.get("secret")
        return get_object_or_404(OrganizationInvitation, secret=secret)

    def dispatch(self, request, *args, **kwargs):
        invitation = self.get_invitation()

        if invitation.is_accepted:
            messages.warning(
                request,
                _("This invitation has already been accepted."),
            )
            return redirect("frontend:organization.selection")
        if not request.user.is_authenticated:
            request.session["invitation_next"] = request.path
            messages.info(
                request,
                _("Please log in or sign up to accept this invitation."),
            )
            return redirect(f"{reverse('account_login')}?next={request.path}")
        return super().dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["invitation"] = self.get_invitation()
        return context

    def post(self, request, *args, **kwargs):
        invitation = self.get_invitation()
        invitation.accepted_by = request.user
        invitation.accepted_at = timezone.now()
        invitation.save()

        OrganizationMembership.objects.get_or_create(
            user=request.user,
            organization=invitation.organization,
            defaults={"role": invitation.role},
        )

        messages.success(
            request,
            _("You have successfully joined {organization}!").format(
                organization=invitation.organization.name
            ),
        )

        request.session.pop("invitation_next", None)
        return redirect(invitation.organization.urls.base)


class InvitationDeleteView(HtmxViewMixin, OrganizationMembershipMixin, DeleteView):
    model = OrganizationInvitation
    http_method_names = ["get", "post"]
    fragments = ("pending-invitations-card",)

    def get_queryset(self):
        return OrganizationInvitation.objects.filter(accepted_by__isnull=True)

    def get_success_url(self):
        return self.object.organization.urls.details

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        organization = self.request.organization
        context["pending_invitations"] = OrganizationInvitation.objects.filter(
            organization=organization, accepted_by__isnull=True
        ).order_by("-created_at")
        return context

    def _check_permission(self):
        return self.request.user.has_perm(
            "core.change_organization", self.request.organization
        )

    def get_object(self):
        if self.request.method == "POST" and self.is_htmx:
            try:
                return super().get_object()
            except Exception:
                return
        return super().get_object()

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        organization = self.object.organization

        if not self._check_permission():
            if not self.is_htmx:
                messages.error(
                    request,
                    _("You do not have permission to delete this invitation."),
                )
            return redirect(organization.urls.details)

        email = self.object.email
        self.object.delete()
        if not self.is_htmx:
            messages.success(
                request,
                _("Invitation for {email} has been deleted.").format(email=email),
            )

        if self.is_htmx and self._get_fragment():
            return self.get(request, *args, **kwargs)

        return redirect(self.get_success_url())
