from django.contrib import messages
from django.core.exceptions import ValidationError
from django.http import Http404, 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.models import (
    ControlPlaneCRD,
    Service,
    ServiceInstance,
    ServiceOffering,
)
from servala.frontend.forms.service import (
    ComputePlanSelectionForm,
    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 = self.request.organization.get_visible_services()

        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, organization=self.request.organization
        )

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["filter_form"] = self.filter_form
        context["deactivated_services"] = (
            self.request.organization.get_deactivated_services()
        )
        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 self.request.organization.get_visible_services()

    @cached_property
    def visible_offerings(self):
        offerings = self.object.offerings.all()
        if self.request.organization.limit_cloudproviders.exists():
            offerings = offerings.filter(
                provider__in=self.request.organization.limit_cloudproviders.all()
            )
        return offerings

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

        # If there's exactly one offering, skip provider selection and go directly to it
        if self.visible_offerings.count() == 1:
            offering = self.visible_offerings.first()
            return redirect(
                "frontend:organization.instance.create",
                organization=self.request.organization.slug,
                slug=self.object.slug,
                pk=offering.pk,
            )

        return super().get(request, *args, **kwargs)

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


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

    def has_permission(self):
        if not self.has_organization_permission():
            return False
        if self.request.organization.limit_cloudproviders.exists():
            return (
                self.get_object().provider
                in self.request.organization.limit_cloudproviders.all()
            )
        return True

    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(
                    "expert-context", self.request.POST.get("custom-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()

    @cached_property
    def plan_form(self):
        data = self.request.POST if self.request.method == "POST" else None
        return ComputePlanSelectionForm(
            data=data, control_plane_crd=self.context_object, prefix="plans"
        )

    def get_instance_form_kwargs(self, ignore_data=False):
        return {
            "initial": {
                "organization": self.request.organization,
                "context": self.context_object,
            },
            "prefix": "expert",
            "data": (
                self.request.POST
                if (self.request.method == "POST" and not ignore_data)
                else None
            ),
        }

    def get_instance_form(self, ignore_data=False):
        if (
            not self.context_object
            or not self.context_object.model_form_class
            or self.hide_expert_mode
        ):
            return

        return self.context_object.model_form_class(
            **self.get_instance_form_kwargs(ignore_data=ignore_data)
        )

    def get_custom_instance_form(self, ignore_data=False):
        if not self.context_object or not self.context_object.custom_model_form_class:
            return
        kwargs = self.get_instance_form_kwargs(ignore_data=ignore_data)
        kwargs["prefix"] = "custom"
        return self.context_object.custom_model_form_class(**kwargs)

    @property
    def is_custom_form(self):
        # Note: "custom form" = user-friendly, subset of fields
        # vs "expert form" = auto-generated (all technical fields)
        return self.request.POST.get("active_form", "expert") == "custom"

    @cached_property
    def hide_expert_mode(self):
        return (
            self.context_object
            and self.context_object.service_definition
            and self.context_object.service_definition.form_config
            and self.context_object.service_definition.hide_expert_mode
        )

    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["context_object"] = self.context_object
        context["hide_expert_mode"] = self.hide_expert_mode
        if self.request.method == "POST":
            if self.is_custom_form:
                context["service_form"] = self.get_instance_form(ignore_data=True)
                context["custom_service_form"] = self.get_custom_instance_form()
            else:
                context["service_form"] = self.get_instance_form()
                context["custom_service_form"] = self.get_custom_instance_form(
                    ignore_data=True
                )
        else:
            context["service_form"] = self.get_instance_form()
            context["custom_service_form"] = self.get_custom_instance_form()

        if self.selected_plane and self.selected_plane.wildcard_dns:
            context["wildcard_dns"] = self.selected_plane.wildcard_dns
            context["organization_namespace"] = self.request.organization.namespace

        if self.context_object:
            context["plan_form"] = self.plan_form
            context["has_available_plans"] = self.plan_form.fields[
                "compute_plan_assignment"
            ].queryset.exists()
            if self.context_object.control_plane.storage_plan_price_per_gib:
                context["storage_plan"] = {
                    "price_per_gib": self.context_object.control_plane.storage_plan_price_per_gib,
                }

        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)

        if not self.plan_form.is_valid():
            return self.render_to_response(context)

        if self.is_custom_form:
            form = self.get_custom_instance_form()
        else:
            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() and self.plan_form.is_valid():
            compute_plan_assignment = self.plan_form.cleaned_data[
                "compute_plan_assignment"
            ]

            try:
                service_instance = ServiceInstance.create_instance(
                    organization=self.request.organization,
                    display_name=form.cleaned_data["display_name"],
                    context=self.context_object,
                    created_by=request.user,
                    spec_data=form.get_nested_data().get("spec"),
                    compute_plan_assignment=compute_plan_assignment,
                )
                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(
                    _("Error creating instance: {error}.").format(error=str(e))
                )
                form.add_error(None, error_message)

        if self.is_custom_form:
            context["custom_service_form"] = form
        else:
            context["service_form"] = form

        return self.render_to_response(context)


class ServiceInstanceMixin:
    model = ServiceInstance
    context_object_name = "instance"
    pk_url_kwarg = "slug"

    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):
        queryset = kwargs.get("queryset") or self.get_queryset()

        # Get the slug from URL (format: "my-instance-123")
        slug = self.kwargs.get(self.pk_url_kwarg)
        if slug is None:
            raise Http404("No slug provided in URL")

        # Extract pk from the slug (everything after the last dash)
        try:
            pk_str = slug.rsplit("-", 1)[-1]
            pk = int(pk_str)
        except (ValueError, IndexError):
            raise Http404(f"Invalid slug format: {slug}")

        try:
            instance = queryset.get(pk=pk)
        except ServiceInstance.DoesNotExist:
            raise Http404("Service instance not found")

        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):
        from servala.core.crd.data import get_nested_data

        context = super().get_context_data(**kwargs)
        if self.object.kubernetes_object and self.object.spec:
            context["spec_fieldsets"] = get_nested_data(self.object.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
        )

        if self.object.compute_plan_assignment:
            context["compute_plan_assignment"] = self.object.compute_plan_assignment

        if (
            self.object.context
            and self.object.context.control_plane.storage_plan_price_per_gib
        ):
            context["storage_plan"] = {
                "price_per_gib": self.object.context.control_plane.storage_plan_price_per_gib,
            }

        context["pricing"] = self.object.calculate_pricing()
        return context


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
        kwargs["prefix"] = "expert"
        return kwargs

    def get_form(self, *args, ignore_data=False, **kwargs):
        if self.hide_expert_mode:
            return
        if not ignore_data:
            return super().get_form(*args, **kwargs)
        cls = self.get_form_class()
        kwargs = self.get_form_kwargs()
        if ignore_data:
            kwargs.pop("data", None)
        return cls(**kwargs)

    def get_custom_form(self, ignore_data=False):
        cls = self.object.context.custom_model_form_class
        if not cls:
            return
        kwargs = self.get_form_kwargs()
        kwargs["prefix"] = "custom"
        if ignore_data:
            kwargs.pop("data", None)
        return cls(**kwargs)

    @cached_property
    def plan_form(self):
        data = self.request.POST if self.request.method == "POST" else None
        initial = self.object.compute_plan_assignment if self.object else None
        return ComputePlanSelectionForm(
            data=data,
            control_plane_crd=self.object.context if self.object else None,
            prefix="plans",
            initial={"compute_plan_assignment": initial} if initial else None,
        )

    @property
    def is_custom_form(self):
        # Note: "custom form" = user-friendly, subset of fields
        # vs "expert form" = auto-generated (all technical fields)
        return self.request.POST.get("active_form", "expert") == "custom"

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

        if self.is_custom_form:
            form = self.get_custom_form()
        else:
            form = self.get_form()

        if form.is_valid() and self.plan_form.is_valid():
            return self.form_valid(form)
        return self.form_invalid(form)

    @cached_property
    def hide_expert_mode(self):
        return (
            self.object
            and self.object.context
            and self.object.context.service_definition
            and self.object.context.service_definition.form_config
            and self.object.context.service_definition.hide_expert_mode
        )

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

        # Check if a form was passed (e.g., from form_invalid)
        form_from_kwargs = kwargs.get("form")

        if self.request.method == "POST":
            if self.is_custom_form:
                # Use the form with errors if passed, otherwise create new
                context["custom_form"] = form_from_kwargs or self.get_custom_form()
                context["form"] = self.get_form(ignore_data=True)
            else:
                # Use the form with errors if passed, otherwise create new
                context["form"] = form_from_kwargs or self.get_form()
                context["custom_form"] = self.get_custom_form(ignore_data=True)
        else:
            context["custom_form"] = self.get_custom_form()

        if self.object and self.object.context:
            context["plan_form"] = self.plan_form
            if self.object.context.control_plane.storage_plan_price_per_gib:
                context["storage_plan"] = {
                    "price_per_gib": self.object.context.control_plane.storage_plan_price_per_gib,
                }

        return context

    def _deep_merge(self, base, update):
        for key, value in update.items():
            if key in base and isinstance(base[key], dict) and isinstance(value, dict):
                self._deep_merge(base[key], value)
            else:
                base[key] = value
        return base

    def form_valid(self, form):
        try:
            form_data = form.get_nested_data()
            spec_data = form_data.get("spec")

            if self.is_custom_form:
                current_spec = dict(self.object.spec) if self.object.spec else {}
                spec_data = self._deep_merge(current_spec, spec_data)

            if display_name := form_data.get("display_name"):
                self.object.display_name = display_name

            compute_plan_assignment = None
            if self.plan_form.is_valid():
                compute_plan_assignment = self.plan_form.cleaned_data.get(
                    "compute_plan_assignment"
                )

            self.object.update_spec(
                spec_data=spec_data,
                updated_by=self.request.user,
                compute_plan_assignment=compute_plan_assignment,
            )
            messages.success(
                self.request,
                _("Service instance '{name}' updated successfully.").format(
                    name=self.object.display_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(
                _("Error updating instance: {error}.").format(error=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.display_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.display_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)
