import json

from django import forms
from django.core.exceptions import ValidationError
from django.forms.widgets import NumberInput


class DynamicArrayWidget(forms.Widget):
    """
    Widget for dynamic array input fields with add/remove functionality.
    Renders as a list of input fields with + and - buttons.
    """

    template_name = "frontend/forms/dynamic_array.html"

    def __init__(self, attrs=None, base_widget=None):
        super().__init__(attrs)
        self.base_widget = base_widget or forms.TextInput()

    def format_value(self, value):
        if value is None or value == "null":
            return []
        if isinstance(value, str):
            if value.strip() == "" or value.strip().lower() == "null":
                return []
            try:
                parsed = json.loads(value)
                if parsed is None:
                    return []
                if isinstance(parsed, list):
                    return [item for item in parsed if item is not None]
                return [parsed]
            except (json.JSONDecodeError, TypeError):
                return [value] if value else []
        if isinstance(value, list):
            return [item for item in value if item is not None]
        if value == "":
            return []
        return [str(value)]

    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        value_list = self.format_value(value)
        if not isinstance(value_list, list):
            value_list = []
        context["value_list"] = value_list
        context["value_list_json"] = json.dumps(value_list)
        return context

    def value_from_datadict(self, data, files, name):
        value = data.get(name)
        if value is None:
            return []
        if value == "":
            return []

        if isinstance(value, list):
            return [item for item in value if item is not None and str(item).strip()]

        if isinstance(value, str):
            if value.strip() == "" or value.strip().lower() == "null":
                return []
            try:
                parsed = json.loads(value)
                if parsed is None:
                    return []
                if isinstance(parsed, list):
                    return [
                        item
                        for item in parsed
                        if item is not None and str(item).strip()
                    ]
                return [parsed] if str(parsed).strip() else []
            except (json.JSONDecodeError, TypeError):
                return [value] if value.strip() else []

        return []


class DynamicArrayField(forms.JSONField):
    """
    Custom field that works with DynamicArrayWidget.
    Handles the conversion between list data and JSON for k8s generated fields.
    """

    widget = DynamicArrayWidget

    def __init__(self, **kwargs):
        self.array_validation = kwargs.pop("array_validation", {})
        kwargs.setdefault("widget", DynamicArrayWidget())
        super().__init__(**kwargs)

    def to_python(self, value):
        """Convert the value to a Python list"""
        if not value:
            return []

        if isinstance(value, list):
            return [item for item in value if item is not None and str(item).strip()]

        if isinstance(value, str):
            if value.strip() == "" or value.strip().lower() == "null":
                return []
            try:
                parsed = json.loads(value)
                if not parsed:
                    return []
                if isinstance(parsed, list):
                    return [item for item in parsed if item is not None]
                return [parsed]
            except (json.JSONDecodeError, TypeError, ValueError):
                raise ValidationError(f"Invalid JSON: {value}")

        return []

    def prepare_value(self, value):
        """Prepare value for widget rendering"""
        if value is None:
            return []
        if isinstance(value, list):
            return [item for item in value if item is not None]
        if isinstance(value, str):
            if value.strip() == "" or value.strip().lower() == "null":
                return []
            try:
                parsed = json.loads(value)
                if parsed is None:
                    return []
                if isinstance(parsed, list):
                    return [item for item in parsed if item is not None]
                return [parsed]
            except (json.JSONDecodeError, TypeError):
                return [value] if value else []
        return [str(value)]

    def bound_data(self, data, initial):
        """Handle bound data properly to avoid JSON parsing issues"""
        if data is None:
            return initial
        if isinstance(data, list):
            return [item for item in data if item is not None and str(item).strip()]
        if isinstance(data, str):
            if data.strip() == "" or data.strip().lower() == "null":
                return []
            try:
                parsed = json.loads(data)
                if parsed is None:
                    return []
                if isinstance(parsed, list):
                    return [
                        item
                        for item in parsed
                        if item is not None and str(item).strip()
                    ]
                return [parsed] if str(parsed).strip() else []
            except (json.JSONDecodeError, TypeError):
                return [data] if data and data.strip() else []
        return data

    def validate(self, value):
        if not self.required and (not value or value == []):
            return
        super().validate(value)
        if value:
            self._validate_array_constraints(value)

    def _validate_array_constraints(self, value):
        if not isinstance(value, list):
            return

        if min_items := self.array_validation.get("min_items"):
            if len(value) < min_items:
                raise ValidationError(
                    f"Array must contain at least {min_items} item(s). Currently has {len(value)}."
                )

        if max_items := self.array_validation.get("max_items"):
            if len(value) > max_items:
                raise ValidationError(
                    f"Array must contain at most {max_items} item(s). Currently has {len(value)}."
                )

        if self.array_validation.get("unique_items"):
            if len(value) != len(set(value)):
                raise ValidationError("All array items must be unique.")

        if items_schema := self.array_validation.get("items_schema"):
            item_type = items_schema.get("type")

            if item_type == "string":
                for i, item in enumerate(value):
                    if not isinstance(item, str):
                        raise ValidationError(f"Item {i + 1} must be a string.")

                    if min_length := items_schema.get("minLength"):
                        if len(item) < min_length:
                            raise ValidationError(
                                f"Item {i + 1} must be at least {min_length} characters long."
                            )

                    if max_length := items_schema.get("maxLength"):
                        if len(item) > max_length:
                            raise ValidationError(
                                f"Item {i + 1} must be at most {max_length} characters long."
                            )

                    if pattern := items_schema.get("pattern"):
                        import re

                        if not re.match(pattern, item):
                            raise ValidationError(
                                f"Item {i + 1} does not match the required pattern."
                            )

                    if enum_values := items_schema.get("enum"):
                        if item not in enum_values:
                            raise ValidationError(
                                f"Item {i + 1} must be one of: {', '.join(enum_values)}"
                            )


class NumberInputWithAddon(NumberInput):
    """
    Widget for number input fields with a suffix add-on (e.g., "Gi", "MB").
    Renders as a Bootstrap input-group with the suffix displayed as an add-on.
    """

    template_name = "frontend/forms/number_input_with_addon.html"

    def __init__(self, addon_text="", attrs=None):
        super().__init__(attrs)
        self.addon_text = addon_text

    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        context["widget"]["addon_text"] = self.addon_text
        return context
