"""
HTTP views for listing and executing Django management commands.
"""

import logging
from functools import wraps
from typing import Any

from django import forms
from django.contrib.admin.sites import site as admin_site
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView, TemplateView

from .conf import is_command_allowed
from .executor import execute_command
from .forms import build_command_form
from .introspection import NON_FIELD_ACTIONS, discover_commands, introspect_command

logger = logging.getLogger(__name__)


def superuser_required(view_func):
    """
    Decorator to ensure user is authenticated superuser.

    Redirects unauthenticated users to login page.
    Raises PermissionDenied for authenticated non-superusers.

    Args:
        view_func: View function or method to wrap

    Returns:
        Wrapped view function with superuser check
    """
    @wraps(view_func)
    def wrapper(request: HttpRequest, *args, **kwargs):
        if not request.user.is_authenticated:
            login_url = reverse('admin:login')
            return redirect(f"{login_url}?next={request.path}")

        if not request.user.is_superuser:
            raise PermissionDenied("Superuser access required")

        return view_func(request, *args, **kwargs)

    return wrapper


class CommandListView(TemplateView):
    """
    Display list of all management commands grouped by app.

    Shows all discovered Django management commands in a table,
    organized by source application. Each command links to its
    execution form.
    """

    template_name = 'admin/management_ui/commands/command_list.html'

    def get_context_data(self, **kwargs) -> dict[str, Any]:
        """Build context for template rendering."""
        context = super().get_context_data(**kwargs)

        # Discover commands and filter by allowlist
        commands_by_app = {
            app: [cmd for cmd in cmds if is_command_allowed(cmd.name)]
            for app, cmds in discover_commands().items()
            if any(is_command_allowed(cmd.name) for cmd in cmds)
        }

        # Sort apps alphabetically
        sorted_apps = dict(sorted(commands_by_app.items()))

        # Sort commands within each app
        for app_label in sorted_apps:
            sorted_apps[app_label] = sorted(
                sorted_apps[app_label],
                key=lambda cmd: cmd.name,
            )

        # Add admin context for base template
        context.update({
            'commands_by_app': sorted_apps,
            'title': _('Management Commands'),
            'has_permission': True,
            'site_title': admin_site.site_title,
            'site_header': admin_site.site_header,
            'site_url': reverse('admin:index'),
            'available_apps': [],  # Empty to avoid sidebar conflicts
        })

        return context

    def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
        """Wrap dispatch with superuser check."""
        return superuser_required(super().dispatch)(request, *args, **kwargs)


class CommandFormView(FormView):
    """
    Display form for command execution and show results.

    GET: Renders dynamic form for command arguments
    POST: Executes command with form data and displays result
    """

    template_name = 'admin/management_ui/commands/command_form.html'
    success_url = None  # Stay on same page after POST

    def get_form_class(self) -> type:
        """Build form class dynamically from command arguments."""
        command_name = self.kwargs['command_name']

        try:
            return build_command_form(command_name)
        except (KeyError, ValueError) as e:
            logger.error('Failed to build form for command %s: %s', command_name, str(e))
            raise Http404(f"Command '{command_name}' not found or cannot be introspected")

    def get_form_kwargs(self) -> dict[str, Any]:
        """
        Build kwargs for form instantiation.

        Override to include request.FILES for file upload support.
        Django's FormView doesn't include FILES by default.

        Returns:
            Dict with 'data' and 'files' keys for form instantiation
        """
        kwargs = super().get_form_kwargs()

        # Include uploaded files for POST requests
        if self.request.method in ('POST', 'PUT'):
            kwargs['files'] = self.request.FILES

        return kwargs

    def get_context_data(self, **kwargs) -> dict[str, Any]:
        """Build context for template rendering."""
        context = super().get_context_data(**kwargs)
        command_name = self.kwargs['command_name']

        try:
            command_args = introspect_command(command_name)
        except (KeyError, ValueError) as e:
            logger.error('Failed to introspect command %s: %s', command_name, str(e))
            raise Http404(f"Command '{command_name}' not found or cannot be introspected")

        # Separate fields into command-specific and BaseCommand groups
        command_field_names = []
        base_command_field_names = []

        for arg_info in command_args.arguments:
            # Skip help and version (no form fields for them)
            if arg_info.action in NON_FIELD_ACTIONS:
                continue

            if arg_info.is_base_command_argument:
                base_command_field_names.append(arg_info.dest)
            else:
                command_field_names.append(arg_info.dest)

        # Add command metadata
        context.update({
            'command_name': command_name,
            'command_app_label': command_args.app_label,
            'command_help': command_args.help_text,
            'title': _('Run Command: %(command_name)s') % {'command_name': command_name},
            'has_permission': True,
            'site_title': admin_site.site_title,
            'site_header': admin_site.site_header,
            'site_url': reverse('admin:index'),
            'available_apps': [],  # Empty to avoid sidebar conflicts
            'command_field_names': command_field_names,
            'base_command_field_names': base_command_field_names,
        })

        # Add result if present (set in form_valid)
        if hasattr(self, '_command_result'):
            context['result'] = self._command_result

        return context

    def form_valid(self, form: forms.Form) -> HttpResponse:
        """Execute command with validated form data."""
        command_name = self.kwargs['command_name']

        # Execute command and capture result
        result = execute_command(command_name, form.cleaned_data)

        # Store result for context
        self._command_result = result

        # Re-render form with result (don't redirect)
        return self.render_to_response(self.get_context_data(form=form))

    def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
        """Wrap dispatch with superuser check and command permission guard."""
        command_name = self.kwargs.get('command_name', '')
        if not is_command_allowed(command_name):
            raise Http404(f"Command '{command_name}' not found")
        return superuser_required(super().dispatch)(request, *args, **kwargs)
