import re
from typing import Any, Optional

from django import forms
from django.forms import Field

from .schemas import FieldChoice, FieldSchema, FormMeta, FormSchema


def create_form_instance(
    form_class: type[forms.BaseForm],
    data: Optional[dict] = None,
    files: Optional[dict] = None,
    **kwargs,
) -> forms.BaseForm:
    """
    Create a form instance, gracefully handling kwargs that the form doesn't accept.
    
    Some Django forms (like allauth's) accept `request` in __init__, others don't.
    This function tries with all kwargs first, then progressively removes kwargs
    that cause TypeErrors until instantiation succeeds.
    """
    # Common kwargs that forms may or may not accept
    optional_kwargs = ['request', 'user', 'instance']
    
    # Build init kwargs
    init_kwargs = dict(kwargs)
    if data is not None:
        init_kwargs['data'] = data
    if files is not None:
        init_kwargs['files'] = files
    
    while True:
        try:
            return form_class(**init_kwargs)
        except TypeError as e:
            error_msg = str(e)
            
            # Check if it's an unexpected keyword argument error
            if "unexpected keyword argument" not in error_msg:
                raise
            
            # Find which kwarg caused the problem and remove it
            removed = False
            for kwarg in optional_kwargs:
                if f"'{kwarg}'" in error_msg and kwarg in init_kwargs:
                    init_kwargs.pop(kwarg)
                    removed = True
                    break
            
            # If we couldn't identify/remove the problematic kwarg, re-raise
            if not removed:
                raise


def _get_choices(field: Field) -> Optional[list[FieldChoice]]:
    """
    Extract choices from a field, handling ModelChoiceField properly.
    ModelChoiceField returns ModelChoiceIteratorValue which is not JSON serializable.
    """
    if not hasattr(field, "choices"):
        return None

    choices: list[FieldChoice] = []
    for raw_value, label in field.choices:
        value = getattr(
            raw_value, "value", raw_value
        )  # ModelChoiceIteratorValue -> .value
        choices.append(FieldChoice(value=str(value), label=str(label)))

    return choices


def _get_initial(value: Any) -> Any:
    """Convert initial value to JSON-serializable format."""
    if value is None:
        return None
    if hasattr(value, "isoformat"):
        return value.isoformat()
    if hasattr(value, "pk"):
        return value.pk
    if hasattr(value, "__iter__") and not isinstance(value, (str, bytes)):
        return [item.pk if hasattr(item, "pk") else item for item in value]
    return value


def _class_name_to_title(name: str) -> str:
    """
    Convert a class name to a human-readable title.
    e.g., 'LoginForm' -> 'Login', 'ResetPasswordForm' -> 'Reset Password'
    """
    # Remove 'Form' suffix
    name = re.sub(r"Form$", "", name)
    # Insert spaces before capital letters
    name = re.sub(r"([a-z])([A-Z])", r"\1 \2", name)
    return name


def _class_name_to_slug(name: str) -> str:
    """
    Convert a class name to a slug.
    e.g., 'LoginForm' -> 'login', 'ResetPasswordForm' -> 'reset_password'
    """
    # Remove 'Form' suffix
    name = re.sub(r"Form$", "", name)
    # Insert underscores before capital letters and lowercase
    name = re.sub(r"([a-z])([A-Z])", r"\1_\2", name)
    return name.lower()


def build_form_schema(
    form_class: type[forms.BaseForm],
    data: Optional[dict] = None,
    **kwargs,
) -> FormSchema:
    """
    Produce a FormSchema for the given Django form class and (optional) data.

    The form class can define metadata via an inner Meta class:

        class MyForm(forms.Form):
            class Meta:
                form_name = "my_form"
                title = "My Form Title"
                subtitle = "Optional description"
                submit_label = "Submit"

                # Frontend behavior (optional)
                refetch_schema_on_validate = False  # Set True for dynamic choice fields
                live_validation = True              # Set False to disable live validation
                live_form_errors = False            # Set True to show form errors live

    If not provided, sensible defaults are derived from the class name.
    """
    form = create_form_instance(form_class, data=data, **kwargs)

    # Extract metadata from form's Meta class
    form_meta = getattr(form_class, "Meta", None)

    # Get form name (used as identifier)
    name = getattr(form_meta, "form_name", None)
    if name is None:
        name = _class_name_to_slug(form_class.__name__)

    # Get title (human-readable heading)
    title = getattr(form_meta, "title", None)
    if title is None:
        title = _class_name_to_title(form_class.__name__)

    # Get optional subtitle
    subtitle = getattr(form_meta, "subtitle", None)

    # Get submit button label
    submit_label = getattr(form_meta, "submit_label", None)
    if submit_label is None:
        submit_label = "Submit"

    # Build frontend behavior metadata
    frontend_meta = FormMeta(
        refetch_schema_on_validate=getattr(form_meta, "refetch_schema_on_validate", False),
        live_validation=getattr(form_meta, "live_validation", True),
        live_form_errors=getattr(form_meta, "live_form_errors", False),
    )

    return FormSchema(
        name=name,
        title=title,
        subtitle=subtitle,
        submit_label=submit_label,
        fields=[
            FieldSchema(
                name=name,
                label=str(field.label or name.replace("_", " ").title()),
                type=getattr(field.widget, "input_type", "text"),
                widget=field.widget.__class__.__name__,
                required=field.required,
                disabled=field.disabled,
                help_text=str(field.help_text) if field.help_text else "",
                initial=_get_initial(field.initial),
                max_length=getattr(field, "max_length", None),
                min_length=getattr(field, "min_length", None),
                choices=_get_choices(field),
            )
            for name, field in form.fields.items()
        ],
        meta=frontend_meta,
    )
