"""
This module provides mixins for Django views that enhance CRUD operations with HTMX support,
filtering capabilities, and modal interactions.

Key Components:
- HTMXFilterSetMixin: Adds HTMX attributes to filter forms for dynamic updates
- PowerCRUDMixin: Main mixin that provides CRUD view enhancements with HTMX and modal support
"""

from django.shortcuts import render
from django.template.response import TemplateResponse

import json
from neapolitan.views import Role
from powercrud.logging import get_logger
from .config_mixin import resolve_config

log = get_logger(__name__)


class HtmxMixin:
    """
    Provides htmx (including modal) and other styling functionality for powercrud views.
    """

    def get_framework_styles(self):
        """
        Get framework-specific styles. Override this method and add
        the new framework name as a key to the returned dictionary.

        Returns:
            dict: Framework-specific style configurations
        """

        return {
            "daisyUI": {
                # base class for all buttons
                "base": "btn ",
                # attributes for filter form fields
                "filter_attrs": {
                    "text": {
                        "class": "input input-bordered input-sm w-full text-xs h-10 min-h-10"
                    },
                    "select": {
                        "class": "select select-bordered select-sm w-full text-xs h-10 min-h-10"
                    },
                    "multiselect": {
                        "class": "select select-bordered select-sm w-full text-xs",
                        "size": "5",
                        "style": "min-height: 8rem; max-height: 8rem; overflow-y: auto;",
                    },
                    "date": {
                        "class": "input input-bordered input-sm w-full text-xs h-10 min-h-10",
                        "type": "date",
                    },
                    "number": {
                        "class": "input input-bordered input-sm w-full text-xs h-10 min-h-10",
                        "step": "any",
                    },
                    "time": {
                        "class": "input input-bordered input-sm w-full text-xs h-10 min-h-10",
                        "type": "time",
                    },
                    "default": {
                        "class": "input input-bordered input-sm w-full text-xs h-10 min-h-10"
                    },
                },
                # set colours for the action buttons
                "actions": {
                    "View": "btn-info",
                    "Edit": "btn-primary",
                    "Delete": "btn-error",
                },
                # default colour for extra action buttons
                "extra_default": "btn-primary",
                # modal class attributes
                "modal_attrs": f'onclick="{self.get_modal_id()[1:]}.showModal()"',
            },
        }

    def get_original_target(self):
        """
        Retrieve the original HTMX target from the session.

        This method is called in get_context_data() to provide the original target
        in the context for templates.

        Returns:
            str or None: The original HTMX target or None if not set
        """
        return resolve_config(self).default_htmx_target

    def get_use_htmx(self):
        """
        Determine if HTMX should be used.

        This method is called in multiple places, including get_context_data(),
        get_htmx_target(), and get_use_modal(), to check if HTMX functionality
        should be enabled.

        Returns:
            bool: True if HTMX should be used, False otherwise
        """
        return bool(resolve_config(self).use_htmx_enabled)

    def get_use_modal(self):
        """
        Determine if modal functionality should be used.

        This method is called in get_context_data() to set the 'use_modal' context
        variable for templates. It requires HTMX to be enabled.

        Returns:
            bool: True if modal should be used and HTMX is enabled, False otherwise
        """
        return bool(resolve_config(self).use_modal_enabled)

    def get_modal_id(self):
        """
        Get the ID for the modal element.

        This method is called in get_framework_styles() to set the modal attributes

        Returns:
            str: The modal ID with a '#' prefix
        """
        return f"#{resolve_config(self).modal_id_resolved}"

    def get_modal_target(self):
        """
        Get the target element ID for the modal content.

        This method is called in get_htmx_target() when use_modal is True to
        determine where to render the modal content.

        Returns:
            str: The modal target ID with a '#' prefix
        """
        return f"#{resolve_config(self).modal_target_resolved}"

    def get_hx_trigger(self):
        """
        Get the HX-Trigger value for HTMX responses.

        This method is called in render_to_response() to set the HX-Trigger header
        for HTMX responses. It handles string, numeric, and dictionary values for
        the hx_trigger attribute.

        Returns:
            str or None: The HX-Trigger value as a JSON string, or None if not applicable
        """
        cfg = resolve_config(self)
        if not self.get_use_htmx() or not cfg.hx_trigger:
            return None

        if isinstance(cfg.hx_trigger, (str, int, float)):
            # Convert simple triggers to JSON format
            # 'messagesChanged' becomes '{"messagesChanged":true}'
            return json.dumps({str(cfg.hx_trigger): True})
        elif isinstance(cfg.hx_trigger, dict):
            # Validate all keys are strings
            if not all(isinstance(k, str) for k in cfg.hx_trigger.keys()):
                raise TypeError("HX-Trigger dict keys must be strings")
            return json.dumps(cfg.hx_trigger)
        else:
            raise TypeError(
                "hx_trigger must be either a string or dict with string keys"
            )

    def get_htmx_target(self):
        """
        Determine the HTMX target for rendering responses.

        This method is called in get_context_data() to set the htmx_target context
        variable for templates. It handles different scenarios based on whether
        HTMX and modal functionality are enabled.

        Returns:
            str or None: The HTMX target as a string with '#' prefix, or None if not applicable
        """
        # only if using htmx
        if not self.get_use_htmx():
            htmx_target = None
        elif resolve_config(self).use_modal_enabled:
            htmx_target = self.get_modal_target()
        elif hasattr(self.request, "htmx") and self.request.htmx.target:
            # return the target of the original list request
            htmx_target = self.get_original_target()
        else:
            htmx_target = resolve_config(
                self
            ).default_htmx_target  # Default target for htmx requests

        return htmx_target

    def _prepare_htmx_response(self, response, context=None, form_has_errors=False):
        """
        Prepare an HTMX response with appropriate triggers and headers.
        """
        # Handle modal display for forms with errors
        if form_has_errors and self.get_use_modal():
            # For daisyUI, we need to trigger the showModal() method
            modal_id = self.get_modal_id()[1:]  # Remove the # prefix

            # Create or update HX-Trigger header
            trigger_data = {"showModal": modal_id, "formSubmitError": True}

            # If there's an existing HX-Trigger, merge with it
            existing_trigger = self.get_hx_trigger()
            if existing_trigger:
                # Since get_hx_trigger always returns a JSON string, we can parse it directly
                existing_data = json.loads(existing_trigger)
                trigger_data.update(existing_data)

            response["HX-Trigger"] = json.dumps(trigger_data)

            # Make sure the response targets the modal content
            if self.get_modal_target():
                response["HX-Retarget"] = self.get_modal_target()

        # For successful form submissions
        elif context and context.get("success") is True:
            # Create success trigger
            trigger_data = {
                "formSubmitSuccess": True,
                "modalFormSuccess": True,
                "refreshList": True,
                "refreshUrl": self.request.path,
            }

            # If there's an existing HX-Trigger, merge with it
            existing_trigger = self.get_hx_trigger()
            if existing_trigger:
                existing_data = json.loads(existing_trigger)
                trigger_data.update(existing_data)

            response["HX-Trigger"] = json.dumps(trigger_data)

        # For other cases, just use the existing HX-Trigger if any
        elif self.get_hx_trigger():
            response["HX-Trigger"] = self.get_hx_trigger()

        return response

    def render_to_response(self, context={}):
        """
        Render the response, handling both HTMX and regular requests.
        Ensure modal context is maintained when forms have errors.
        """
        template_names = self.get_template_names()

        # Try the first template (app-specific), fall back to second (generic)
        from django.template.loader import get_template
        from django.template.exceptions import TemplateDoesNotExist

        try:
            # try to use overriden template if it exists
            template_name = template_names[0]
            # this call check if valid template
            get_template(template_name)
        except TemplateDoesNotExist:
            template_name = template_names[1]
            get_template(template_name)
        except Exception as e:
            log.error(f"Unexpected error checking template {template_name}: {str(e)}")
            template_name = template_names[1]

        # Check if this is a form with errors being redisplayed
        form_has_errors = hasattr(self, "form_has_errors") and self.form_has_errors

        if self.request.htmx:
            if self.request.headers.get("X-Redisplay-Object-List"):
                # Use object_list template
                object_list_template = f"{self.templates_path}/object_list.html"

                if self.request.headers.get("X-Filter-Sort-Request"):
                    template_name = f"{object_list_template}#filtered_results"
                else:
                    template_name = f"{object_list_template}#pcrud_content"
            else:
                # Use whatever template was determined normally
                if self.request.headers.get("X-Filter-Sort-Request"):
                    template_name = f"{template_name}#filtered_results"
                else:
                    template_name = f"{template_name}#pcrud_content"

            response = render(
                request=self.request,
                template_name=f"{template_name}",
                context=context,
            )

            # Only set HX-Push-Url for GET requests and when role is LIST
            if self.request.method == "GET" and self.role == Role.LIST:
                clean_params = {}
                for k in self.request.GET:
                    values = self.request.GET.getlist(k)
                    if values and values[-1]:  # Only non-empty
                        clean_params[k] = values[-1]
                if clean_params:
                    from urllib.parse import urlencode

                    canonical_query = urlencode(clean_params)
                    canonical_url = f"{self.request.path}?{canonical_query}"
                else:
                    canonical_url = self.request.path
                response["HX-Push-Url"] = canonical_url

            # Add HX-Trigger for modal if form has errors and modal should be used
            if form_has_errors and self.get_use_modal():
                # Single, simplified trigger
                response["HX-Trigger"] = json.dumps({"formError": True})

                # Make sure the response targets the modal content
                response["HX-Retarget"] = self.get_modal_target()

                # Clear the flag after handling
                self.form_has_errors = False
            elif self.get_hx_trigger():
                response["HX-Trigger"] = self.get_hx_trigger()

            return response
        else:
            return TemplateResponse(
                request=self.request, template=template_name, context=context
            )
