import uuid

from django.http import Http404, HttpResponse, HttpResponseBadRequest
from django.urls import reverse
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.list import ListView
from rest_framework.parsers import FormParser

from mixins.restrictions import RestrictedViewMixin


class AbortSave(Exception):
    pass


class ObjectNameMixin(object):
    def __init__(self, *args, **kwargs):
        if self.model is None:
            self.title = self.context_object_name.title()
            self.title_singular = self.context_object_name_singular.title()
        else:
            self.title_singular = self.model._meta.verbose_name.title()  # Hook
            self.context_object_name_singular = self.title_singular.lower()  # hook
            self.title = self.model._meta.verbose_name_plural.title()  # Hooks
            self.context_object_name = self.title.lower()  # hooks

            self.context_object_name = self.context_object_name.replace(" ", "")
            self.context_object_name_singular = (
                self.context_object_name_singular.replace(" ", "")
            )
        super().__init__(*args, **kwargs)


class ObjectList(RestrictedViewMixin, ObjectNameMixin, ListView):
    allowed_types = ["modal", "widget", "window", "page"]
    window_content = "mixins/window-content/objects.html"
    list_template = None

    page_title = None
    page_subtitle = None

    list_url_name = None
    # WARNING: TAKEN FROM locals()
    list_url_args = ["type"]

    submit_url_name = None
    submit_url_args = ["type"]

    delete_all_url_name = None
    widget_options = None

    extra_buttons = None

    def queryset_mutate(self, queryset):
        pass

    # copied from BaseListView
    def get(self, request, *args, **kwargs):
        type = kwargs.get("type", None)
        if not type:
            return HttpResponseBadRequest("No type specified")
        if type not in self.allowed_types:
            return HttpResponseBadRequest("Invalid type specified")

        self.request = request
        self.object_list = self.get_queryset(**kwargs)

        if isinstance(self.object_list, HttpResponse):
            return self.object_list
        if isinstance(self.object_list, HttpResponseBadRequest):
            return self.object_list

        self.queryset_mutate(self.object_list)
        allow_empty = self.get_allow_empty()

        self.template_name = f"mixins/wm/{type}.html"
        unique = str(uuid.uuid4())[:8]

        list_url_args = {}
        for arg in self.list_url_args:
            if arg in locals():
                list_url_args[arg] = locals()[arg]
            elif arg in kwargs:
                list_url_args[arg] = kwargs[arg]

        orig_type = type
        if type == "page":
            type = "modal"

        if not allow_empty:
            # When pagination is enabled and object_list is a queryset,
            # it's better to do a cheap query than to load the unpaginated
            # queryset in memory.
            if self.get_paginate_by(self.object_list) is not None and hasattr(
                self.object_list, "exists"
            ):
                is_empty = not self.object_list.exists()
            else:
                is_empty = not self.object_list
            if is_empty:
                raise Http404("Empty list")

        submit_url_args = {}
        for arg in self.submit_url_args:
            if arg in locals():
                submit_url_args[arg] = locals()[arg]
            elif arg in kwargs:
                submit_url_args[arg] = kwargs[arg]

        context = self.get_context_data()
        context["title"] = self.title + f" ({type})"
        context["title_singular"] = self.title_singular
        context["unique"] = unique
        context["window_content"] = self.window_content
        context["list_template"] = self.list_template
        context["page_title"] = self.page_title
        context["page_subtitle"] = self.page_subtitle
        context["type"] = type
        context["context_object_name"] = self.context_object_name
        context["context_object_name_singular"] = self.context_object_name_singular

        if self.submit_url_name is not None:
            context["submit_url"] = reverse(
                self.submit_url_name, kwargs=submit_url_args
            )

        if self.list_url_name is not None:
            context["list_url"] = reverse(self.list_url_name, kwargs=list_url_args)

        if self.delete_all_url_name:
            context["delete_all_url"] = reverse(self.delete_all_url_name)
        if self.widget_options:
            context["widget_options"] = self.widget_options
        if self.extra_buttons is not None:
            context["extra_buttons"] = self.extra_buttons

        # Return partials for HTMX
        if self.request.htmx:
            if request.headers["HX-Target"] == self.context_object_name + "-table":
                self.template_name = self.list_template
            elif orig_type == "page":
                self.template_name = self.list_template
            else:
                context["window_content"] = self.list_template
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        self.request = request
        return super().get(request, *args, **kwargs)


class ObjectCreate(RestrictedViewMixin, ObjectNameMixin, CreateView):
    allowed_types = ["modal", "widget", "window", "page"]
    window_content = "mixins/window-content/object-form.html"
    parser_classes = [FormParser]

    page_title = None
    page_subtitle = None

    model = None
    submit_url_name = None
    submit_url_args = ["type"]

    request = None

    # Whether to hide the cancel button in the form
    hide_cancel = False

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.title = "Create " + self.context_object_name_singular

    def post_save(self, obj):
        pass

    def pre_save_mutate(self, user, obj):
        pass

    def form_valid(self, form):
        obj = form.save(commit=False)
        if self.request is None:
            raise Exception("Request is None")
        obj.user = self.request.user
        try:
            self.pre_save_mutate(self.request.user, obj)
        except AbortSave as e:
            context = {"message": f"Failed to save: {e}", "class": "danger"}
            return self.render_to_response(context)
        obj.save()
        form.save_m2m()
        self.post_save(obj)
        context = {"message": "Object created", "class": "success"}
        response = self.render_to_response(context)
        response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
        return response

    def form_invalid(self, form):
        """If the form is invalid, render the invalid form."""
        return self.get(self.request, **self.kwargs, form=form)

    def get(self, request, *args, **kwargs):
        type = kwargs.get("type", None)
        if not type:
            return HttpResponseBadRequest("No type specified")
        if type not in self.allowed_types:
            return HttpResponseBadRequest("Invalid type specified")
        self.template_name = f"mixins/wm/{type}.html"
        unique = str(uuid.uuid4())[:8]

        self.request = request
        self.kwargs = kwargs

        if type == "widget":
            self.hide_cancel = True

        if type == "page":
            type = "modal"

        self.object = None

        submit_url_args = {}
        for arg in self.submit_url_args:
            if arg in locals():
                submit_url_args[arg] = locals()[arg]
            elif arg in kwargs:
                submit_url_args[arg] = kwargs[arg]
        submit_url = reverse(self.submit_url_name, kwargs=submit_url_args)

        context = self.get_context_data()
        form = kwargs.get("form", None)
        if form:
            context["form"] = form
        context["unique"] = unique
        context["window_content"] = self.window_content
        context["context_object_name"] = self.context_object_name
        context["context_object_name_singular"] = self.context_object_name_singular
        context["submit_url"] = submit_url
        context["type"] = type
        context["hide_cancel"] = self.hide_cancel
        if self.page_title:
            context["page_title"] = self.page_title
        if self.page_subtitle:
            context["page_subtitle"] = self.page_subtitle
        response = self.render_to_response(context)
        # response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
        return response

    def post(self, request, *args, **kwargs):
        self.request = request
        self.template_name = "mixins/partials/notify.html"
        return super().post(request, *args, **kwargs)


class ObjectRead(RestrictedViewMixin, ObjectNameMixin, DetailView):
    allowed_types = ["modal", "widget", "window", "page"]
    window_content = "mixins/window-content/object.html"
    detail_template = "mixins/partials/generic-detail.html"

    page_title = None
    page_subtitle = None

    model = None
    # submit_url_name = None

    detail_url_name = None
    # WARNING: TAKEN FROM locals()
    detail_url_args = ["type"]

    request = None

    extra_buttons = None

    def get(self, request, *args, **kwargs):
        type = kwargs.get("type", None)
        if not type:
            return HttpResponseBadRequest("No type specified")
        if type not in self.allowed_types:
            return HttpResponseBadRequest()
        self.template_name = f"mixins/wm/{type}.html"
        unique = str(uuid.uuid4())[:8]

        detail_url_args = {}
        for arg in self.detail_url_args:
            if arg in locals():
                detail_url_args[arg] = locals()[arg]
            elif arg in kwargs:
                detail_url_args[arg] = kwargs[arg]

        self.request = request
        self.kwargs = kwargs
        self.object = self.get_object(**kwargs)
        if isinstance(self.object, HttpResponse):
            return self.object

        orig_type = type
        if type == "page":
            type = "modal"

        context = self.get_context_data()

        context["title"] = self.title + f" ({type})"
        context["title_singular"] = self.title_singular
        context["unique"] = unique
        context["window_content"] = self.window_content
        context["detail_template"] = self.detail_template
        if self.page_title:
            context["page_title"] = self.page_title
        if self.page_subtitle:
            context["page_subtitle"] = self.page_subtitle
        context["type"] = type
        context["context_object_name"] = self.context_object_name
        context["context_object_name_singular"] = self.context_object_name_singular

        if self.detail_url_name is not None:
            context["detail_url"] = reverse(
                self.detail_url_name, kwargs=detail_url_args
            )

        if self.extra_buttons is not None:
            context["extra_buttons"] = self.extra_buttons

        # Return partials for HTMX
        if self.request.htmx:
            if request.headers["HX-Target"] == self.context_object_name + "-info":
                self.template_name = self.detail_template
            elif orig_type == "page":
                self.template_name = self.detail_template
            else:
                context["window_content"] = self.detail_template

        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        self.request = request
        return super().get(request, *args, **kwargs)


class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView):
    allowed_types = ["modal", "widget", "window", "page"]
    window_content = "mixins/window-content/object-form.html"
    parser_classes = [FormParser]

    page_title = None
    page_subtitle = None

    model = None
    submit_url_name = None
    submit_url_args = ["type", "pk"]

    request = None

    # Whether pk is required in the get request
    pk_required = True

    # Whether to hide the cancel button in the form
    hide_cancel = False

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.title = "Update " + self.context_object_name_singular

    def post_save(self, obj):
        pass

    def form_valid(self, form):
        obj = form.save(commit=False)
        if self.request is None:
            raise Exception("Request is None")
        obj.save()
        form.save_m2m()
        self.post_save(obj)
        context = {"message": "Object updated", "class": "success"}
        response = self.render_to_response(context)
        response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
        return response

    def form_invalid(self, form):
        """If the form is invalid, render the invalid form."""
        return self.get(self.request, **self.kwargs, form=form)

    def get(self, request, *args, **kwargs):
        self.request = request
        type = kwargs.get("type", None)
        pk = kwargs.get("pk", None)
        if not type:
            return HttpResponseBadRequest("No type specified")
        if not pk:
            if self.pk_required:
                return HttpResponseBadRequest("No pk specified")
        if type not in self.allowed_types:
            return HttpResponseBadRequest("Invalid type specified")
        self.template_name = f"mixins/wm/{type}.html"
        unique = str(uuid.uuid4())[:8]
        if type == "widget":
            self.hide_cancel = True

        if type == "page":
            type = "modal"

        self.object = self.get_object()

        submit_url_args = {}
        for arg in self.submit_url_args:
            if arg in locals():
                submit_url_args[arg] = locals()[arg]
            elif arg in kwargs:
                submit_url_args[arg] = kwargs[arg]
        submit_url = reverse(self.submit_url_name, kwargs=submit_url_args)

        context = self.get_context_data()
        form = kwargs.get("form", None)
        if form:
            context["form"] = form
        context["title"] = self.title + f" ({type})"
        context["title_singular"] = self.title_singular
        context["unique"] = unique
        context["window_content"] = self.window_content
        context["context_object_name"] = self.context_object_name
        context["context_object_name_singular"] = self.context_object_name_singular
        context["submit_url"] = submit_url
        context["type"] = type
        context["hide_cancel"] = self.hide_cancel
        if self.page_title:
            context["page_title"] = self.page_title
        if self.page_subtitle:
            context["page_subtitle"] = self.page_subtitle
        response = self.render_to_response(context)
        # response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
        return response

    def post(self, request, *args, **kwargs):
        self.request = request
        self.template_name = "mixins/partials/notify.html"
        return super().post(request, *args, **kwargs)


class ObjectDelete(RestrictedViewMixin, ObjectNameMixin, DeleteView):
    model = None
    template_name = "mixins/partials/notify.html"

    # Overriden to prevent success URL from being used
    def delete(self, request, *args, **kwargs):
        """
        Call the delete() method on the fetched object and then redirect to the
        success URL.
        """
        self.object = self.get_object()
        # success_url = self.get_success_url()
        self.object.delete()
        context = {"message": "Object deleted", "class": "success"}
        response = self.render_to_response(context)
        response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
        return response

    # This will be used in newer Django versions, until then we get a warning
    def form_valid(self, form):
        """
        Call the delete() method on the fetched object.
        """
        self.object = self.get_object()
        self.object.delete()
        context = {"message": "Object deleted", "class": "success"}
        response = self.render_to_response(context)
        response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
        return response
