from django.contrib import messages
from django.core.exceptions import ValidationError
from django.http import HttpResponse
from django.shortcuts import redirect
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, UpdateView

from servala.core.crd import deslugify
from servala.core.models import (
    ControlPlaneCRD,
    Service,
    ServiceInstance,
    ServiceOffering,
)
from servala.frontend.forms.service import (
    ControlPlaneSelectForm,
    ServiceFilterForm,
    ServiceInstanceDeleteForm,
    ServiceInstanceFilterForm,
)
from servala.frontend.views.mixins import (
    HtmxUpdateView,
    HtmxViewMixin,
    OrganizationViewMixin,
)


class ServiceListView(OrganizationViewMixin, ListView):
    """View to display all available services for an organization."""

    template_name = "frontend/organizations/services.html"
    context_object_name = "services"
    model = Service
    permission_type = "view"

    def get_queryset(self):
        """Return all services."""
        services = (
            Service.objects.all()
            .select_related("category")
            .prefetch_related("offerings__provider")
        )
        if self.filter_form.is_valid():
            services = self.filter_form.filter_queryset(services)
        return services.distinct()

    @cached_property
    def filter_form(self):
        return ServiceFilterForm(data=self.request.GET or None)

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


class ServiceDetailView(OrganizationViewMixin, DetailView):
    template_name = "frontend/organizations/service_detail.html"
    context_object_name = "service"
    model = Service
    permission_type = "view"

    def get_queryset(self):
        return Service.objects.select_related("category").prefetch_related(
            "offerings",
            "offerings__provider",
        )


class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView):
    template_name = "frontend/organizations/service_offering_detail.html"
    context_object_name = "offering"
    model = ServiceOffering
    permission_type = "view"
    fragments = ("service-form", "control-plane-info")

    def has_permission(self):
        return self.has_organization_permission()

    def get_queryset(self):
        return ServiceOffering.objects.all().select_related(
            "service", "service__category", "provider"
        )

    @cached_property
    def planes(self):
        return self.object.control_planes.all()

    @cached_property
    def select_form(self):
        data = None
        if "control_plane" in self.request.GET:
            data = self.request.GET
        elif self.request.method == "POST" and self.context_object:
            data = {"control_plane": self.context_object.control_plane_id}
        return ControlPlaneSelectForm(data=data, planes=self.planes)

    @cached_property
    def selected_plane(self):
        if self.select_form.is_valid() and self.select_form.cleaned_data:
            return self.select_form.cleaned_data["control_plane"]
        field = self.select_form.fields["control_plane"]
        return field.initial or field.queryset.first()

    @cached_property
    def context_object(self):
        if self.request.method == "POST":
            return ControlPlaneCRD.objects.filter(
                pk=self.request.POST.get("context"),
                # Make sure we don’t use a malicious ID
                control_plane__in=self.planes,
            ).first()
        return ControlPlaneCRD.objects.filter(
            control_plane=self.selected_plane, service_offering=self.object
        ).first()

    def get_instance_form(self):
        if not self.context_object or not self.context_object.model_form_class:
            return None
        return self.context_object.model_form_class(
            data=self.request.POST if self.request.method == "POST" else None,
            initial={
                "organization": self.request.organization,
                "context": self.context_object,
            },
        )

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["select_form"] = self.select_form
        context["has_control_planes"] = self.planes.exists()
        context["selected_plane"] = self.selected_plane
        context["service_form"] = self.get_instance_form()
        return context

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

        if not self.context_object:
            context["form_error"] = True
            return self.render_to_response(context)

        form = self.get_instance_form()
        if not form:  # Should not happen if context_object is valid, but as a safeguard
            messages.error(
                self.request,
                self.organization.add_support_message(
                    _("Could not initialize service form.")
                ),
            )
            return self.render_to_response(context)

        if form.is_valid():
            try:
                service_instance = ServiceInstance.create_instance(
                    organization=self.request.organization,
                    name=form.cleaned_data["name"],
                    context=self.context_object,
                    created_by=request.user,
                    spec_data=form.get_nested_data().get("spec"),
                )
                return redirect(service_instance.urls.base)
            except ValidationError as e:
                form.add_error(None, e.message or str(e))
            except Exception as e:
                error_message = self.organization.add_support_message(
                    _(f"Error creating instance: {str(e)}.")
                )
                form.add_error(None, error_message)

        # If the form is not valid or if the service creation failed, we render it again
        context["service_form"] = form
        return self.render_to_response(context)


class ServiceInstanceMixin:
    model = ServiceInstance
    context_object_name = "instance"
    slug_field = "name"

    def dispatch(self, *args, **kwargs):
        self._has_warned = False
        return super().dispatch(*args, **kwargs)

    def get_queryset(self):
        """Return service instance for the current organization."""
        return ServiceInstance.objects.filter(
            organization=self.request.organization
        ).select_related(
            "context__service_offering__provider",
            "context__control_plane",
            "context__service_definition__service",
        )

    def get_object(self, **kwargs):
        instance = super().get_object(**kwargs)
        if not instance.kubernetes_object and not self._has_warned:
            messages.warning(
                self.request,
                _(
                    "Could not retrieve instance details from Kubernetes. It might have been deleted externally."
                ),
            )
            self._has_warned = True
        return instance


class ServiceInstanceDetailView(
    ServiceInstanceMixin, OrganizationViewMixin, DetailView
):
    template_name = "frontend/organizations/service_instance_detail.html"
    permission_type = "view"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.object.kubernetes_object and self.object.spec:
            context["spec_fieldsets"] = self.get_nested_spec()
        context["has_change_permission"] = self.request.user.has_perm(
            ServiceInstance.get_perm("change"), self.object
        )
        context["has_delete_permission"] = self.request.user.has_perm(
            ServiceInstance.get_perm("delete"), self.object
        )
        return context

    def get_nested_spec(self):
        """
        Organize spec data into fieldsets similar to how the form does it.
        """
        spec = self.object.spec or {}
        if not spec:
            return []

        others = []
        nested_fieldsets = {}

        # First pass: organize fields into nested structures
        for key, value in spec.items():
            if isinstance(value, dict):
                # This is a nested structure
                if key not in nested_fieldsets:
                    nested_fieldsets[key] = {
                        "title": deslugify(key),
                        "fields": [],
                        "fieldsets": {},
                    }

                # Process fields in the nested structure
                for sub_key, sub_value in value.items():
                    if isinstance(sub_value, dict):
                        # Even deeper nesting
                        if sub_key not in nested_fieldsets[key]["fieldsets"]:
                            nested_fieldsets[key]["fieldsets"][sub_key] = {
                                "title": deslugify(sub_key),
                                "fields": [],
                            }

                        # Add fields from the deeper level
                        for leaf_key, leaf_value in sub_value.items():
                            nested_fieldsets[key]["fieldsets"][sub_key][
                                "fields"
                            ].append(
                                {
                                    "key": leaf_key,
                                    "label": deslugify(leaf_key),
                                    "value": leaf_value,
                                }
                            )
                    else:
                        # Add field to parent level
                        nested_fieldsets[key]["fields"].append(
                            {
                                "key": sub_key,
                                "label": deslugify(sub_key),
                                "value": sub_value,
                            }
                        )
            else:
                # This is a top-level field
                others.append(
                    {
                        "key": key,
                        "label": deslugify(key),
                        "value": value,
                    }
                )

        # Second pass: Promote fields based on count
        for group_key, group in list(nested_fieldsets.items()):
            # Promote single sub-fieldsets to parent
            for sub_key, sub_fieldset in list(group["fieldsets"].items()):
                if len(sub_fieldset["fields"]) == 1:
                    field = sub_fieldset["fields"][0]
                    field["label"] = f"{sub_fieldset['title']}: {field['label']}"
                    group["fields"].append(field)
                    del group["fieldsets"][sub_key]

            # Move single-field groups to others
            total_fields = len(group["fields"])
            for sub_fieldset in group["fieldsets"].values():
                total_fields += len(sub_fieldset["fields"])

            if (
                total_fields == 1
                and len(group["fields"]) == 1
                and not group["fieldsets"]
            ):
                field = group["fields"][0]
                field["label"] = f"{group['title']}: {field['label']}"
                others.append(field)
                del nested_fieldsets[group_key]
            elif total_fields == 0:
                del nested_fieldsets[group_key]

        fieldsets = []
        if others:
            fieldsets.append(
                {
                    "title": "General",
                    "fields": others,
                    "fieldsets": {},
                }
            )
        # Create fieldsets from the organized data
        for group in nested_fieldsets.values():
            fieldsets.append(group)

        return fieldsets


class ServiceInstanceUpdateView(
    ServiceInstanceMixin, OrganizationViewMixin, HtmxUpdateView
):
    template_name = "frontend/organizations/service_instance_update.html"
    permission_type = "change"

    def get_form_class(self):
        return self.object.context.model_form_class

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["instance"] = self.object.spec_object
        return kwargs

    def form_valid(self, form):
        try:
            spec_data = form.get_nested_data().get("spec")
            self.object.update_spec(spec_data=spec_data, updated_by=self.request.user)
            messages.success(
                self.request,
                _("Service instance '{name}' updated successfully.").format(
                    name=self.object.name
                ),
            )
            return redirect(self.object.urls.base)
        except ValidationError as e:
            form.add_error(None, e.message or str(e))
            return self.form_invalid(form)
        except Exception as e:
            error_message = self.organization.add_support_message(
                _(f"Error updating instance: {str(e)}.")
            )
            form.add_error(None, error_message)
            return self.form_invalid(form)

    def get_success_url(self):
        return self.object.urls.base


class ServiceInstanceListView(OrganizationViewMixin, ListView):
    template_name = "frontend/organizations/service_instances.html"
    context_object_name = "instances"
    model = ServiceInstance
    permission_type = "view"

    @cached_property
    def filter_form(self):
        return ServiceInstanceFilterForm(data=self.request.GET or None)

    def get_queryset(self):
        """Return all service instances for the current organization with filtering."""
        queryset = ServiceInstance.objects.filter(
            organization=self.request.organization,
        ).select_related(
            "context__service_offering__provider",
            "context__control_plane",
            "context__service_definition__service",
        )
        if self.filter_form.is_valid():
            queryset = self.filter_form.filter_queryset(queryset)
        return queryset.order_by("-created_at")

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["organization"] = self.request.organization
        context["filter_form"] = self.filter_form
        return context


class ServiceInstanceDeleteView(
    ServiceInstanceMixin, OrganizationViewMixin, HtmxViewMixin, UpdateView
):
    template_name = "frontend/organizations/service_instance_delete_form.html"
    form_class = ServiceInstanceDeleteForm
    permission_type = "delete"

    def form_valid(self, form):
        try:
            self.object.delete(user=self.request.user)
            messages.success(
                self.request,
                _("Service instance '{name}' has been scheduled for deletion.").format(
                    name=self.object.name
                ),
            )
            response = HttpResponse()
            response["HX-Redirect"] = self.get_success_url()
            return response
        except Exception as e:
            messages.error(
                self.request,
                self.organization.add_support_message(
                    _(
                        f"An error occurred while trying to delete instance '{self.object.name}': {str(e)}."
                    )
                ),
            )
            response = HttpResponse()
            response["HX-Redirect"] = str(self.object.urls.base)
            return response

    def get_success_url(self):
        return str(self.request.organization.urls.instances)
