"""
Dynamic form generation from argparse argument metadata.
"""

import pathlib

from django import forms
from django.core.files.uploadedfile import UploadedFile
from django.utils.translation import gettext_lazy as _

from .introspection import NON_FIELD_ACTIONS, ArgumentInfo, introspect_command


class PathInputWidget(forms.MultiWidget):
    """
    Widget that renders both file upload and text input for Path arguments.

    Users can switch between 'Upload a file' and 'Enter path as text' modes
    via a toggle link. Only one mode is active at a time.
    """

    template_name = 'admin/management_ui/commands/widgets/path_input.html'

    def __init__(self, attrs=None):
        widgets = [
            forms.ClearableFileInput(attrs={'class': 'path-file-input'}),
            forms.TextInput(attrs={'class': 'path-text-input'}),
        ]
        super().__init__(widgets, attrs)

    def use_required_attribute(self, initial):
        # Never add HTML5 'required' to sub-widgets — only one mode is active
        # at a time, so the hidden input would block form submission.
        # Validation is handled server-side by PathField.clean().
        return False

    def decompress(self, value):
        """Split combined value into [file, text] for each sub-widget."""
        if isinstance(value, str):
            return [None, value]
        return [value, '']


class PathField(forms.Field):
    """
    Form field for Path arguments that accepts either file upload or text path.

    Returns:
        UploadedFile if file was uploaded, or str if text path was entered.
        None if nothing provided and field is not required.
    """

    widget = PathInputWidget

    def clean(self, value):
        """Validate and return either the uploaded file or text path."""
        # value comes as list from MultiWidget: [file_or_None, text_or_empty]
        if not isinstance(value, (list, tuple)) or len(value) != 2:
            if self.required:
                raise forms.ValidationError(self.error_messages.get('required', 'This field is required.'))
            return None

        file_value, text_value = value[0], value[1]

        # File upload takes priority if present
        if file_value and isinstance(file_value, UploadedFile):
            return file_value

        # Text path
        if text_value and text_value.strip():
            return text_value.strip()

        # Nothing provided
        if self.required:
            raise forms.ValidationError(self.error_messages.get('required', 'This field is required.'))
        return None


def build_command_form(command_name: str) -> type[forms.Form]:
    """
    Create a Django Form class dynamically for a management command.

    Introspects the command's argparse configuration and converts each
    argument to an appropriate Django form field. The form class is
    created dynamically using type().

    Args:
        command_name: Name of the management command

    Returns:
        Dynamically created Form class (not an instance)

    Raises:
        KeyError: If command not found in registered commands

    Example:
        MigrateForm = build_command_form('migrate')
        form = MigrateForm(data={'database': 'default'})
        if form.is_valid():
            # Use form.cleaned_data
    """
    command_args = introspect_command(command_name)
    fields = {}

    for arg_info in command_args.arguments:
        field = _map_argument_to_field(arg_info)
        # Skip help and version (None fields)
        if field is not None:
            fields[arg_info.dest] = field

    # Create Form class dynamically
    form_class = type(
        f'{command_name.capitalize()}Form',
        (forms.Form,),
        fields,
    )

    return form_class


def _map_argument_to_field(arg_info: ArgumentInfo) -> forms.Field | None:
    """
    Convert ArgumentInfo to appropriate Django form field.

    Maps argparse argument types and actions to Django form field classes.
    Handles choices, boolean flags, numeric types, and multi-value arguments.

    Args:
        arg_info: Metadata about the argparse argument

    Returns:
        Configured Django form field instance, or None for help/version actions

    Field Mapping:
        - action store_true → BooleanField(required=False, initial=False)
        - action store_false → BooleanField(required=False, initial=True)
        - choices → ChoiceField (with blank choice if optional)
        - type=int → IntegerField
        - type=float → FloatField
        - nargs (+, *, ?) → CharField with multi-value help text
        - default → CharField
    """
    # Skip help and version actions (they exit the process, cannot be form fields)
    if arg_info.action in NON_FIELD_ACTIONS:
        return None

    help_text = _build_help_text(arg_info)

    # Handle boolean flags FIRST (always optional, don't use field_kwargs)
    if arg_info.action == 'store_true':
        return forms.BooleanField(required=False, help_text=help_text, initial=False)

    if arg_info.action == 'store_false':
        return forms.BooleanField(required=False, help_text=help_text, initial=True)

    # For non-boolean fields, build common parameters
    required = arg_info.required or arg_info.is_positional
    initial = None
    if arg_info.default is not None and arg_info.default != '==SUPPRESS==':
        initial = arg_info.default

    # Handle Path types (file upload or text path input)
    if arg_info.type is not None:
        try:
            if issubclass(arg_info.type, pathlib.PurePath):
                upload_hint = _('Upload a file or enter path as text')
                path_help = f'{help_text} ({upload_hint})' if help_text else upload_hint
                return PathField(
                    required=required,
                    help_text=path_help,
                )
        except TypeError:
            # issubclass() raises TypeError for non-class types
            pass

    # Handle choices
    if arg_info.choices:
        choices = [(choice, choice) for choice in arg_info.choices]
        # Add blank choice for optional fields (allows user to override default)
        if not required:
            choices.insert(0, ('', '--------'))
        return forms.ChoiceField(
            choices=choices,
            required=required,
            initial=initial,
            help_text=help_text,
        )

    # Handle typed arguments
    if arg_info.type is int:
        return forms.IntegerField(required=required, initial=initial, help_text=help_text)

    if arg_info.type is float:
        return forms.FloatField(required=required, initial=initial, help_text=help_text)

    # Default to CharField for strings and unknown types
    return forms.CharField(required=required, initial=initial, help_text=help_text)


def _build_help_text(arg_info: ArgumentInfo) -> str:
    """
    Build comprehensive help text for form field.

    Combines argparse help text with additional hints for multi-value
    arguments (nargs). Provides clear guidance for users filling the form.

    Args:
        arg_info: Argument metadata

    Returns:
        Formatted help text string
    """
    help_parts = []

    if arg_info.help_text:
        help_parts.append(arg_info.help_text)

    # Add multi-value hint for nargs arguments
    if arg_info.nargs == '+':
        help_parts.append(_('(One or more space-separated values)'))
    elif arg_info.nargs == '*':
        help_parts.append(_('(Zero or more space-separated values)'))
    elif arg_info.nargs == '?':
        help_parts.append(_('(Optional, space-separated if multiple)'))
    elif isinstance(arg_info.nargs, int):
        help_parts.append(_('(Exactly %(count)s space-separated values)') % {'count': arg_info.nargs})

    # Convert lazy translation objects to strings before joining
    return ' '.join(str(part) for part in help_parts) if help_parts else ''
