from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
from django.db import models
from django.utils.translation import gettext_lazy as _

from servala.core.crd.utils import deslugify
from servala.core.models import ServiceInstance
from servala.frontend.forms.widgets import DynamicArrayField


class CRDModel(models.Model):
    """Base class for all virtual CRD models"""

    def __init__(self, **kwargs):
        if spec := kwargs.pop("spec", None):
            kwargs.update(unnest_data({"spec": spec}))
        super().__init__(**kwargs)

    class Meta:
        abstract = True


def generate_django_model(schema, group, version, kind):
    """
    Generates a virtual Django model from a Kubernetes CRD's OpenAPI v3 schema.
    """
    # We always need these fields to know our display name and our full namespace
    model_fields = {"__module__": "crd_models"}
    for field_name in ("display_name", "context"):
        model_fields[field_name] = duplicate_field(field_name, ServiceInstance)

    # All other fields are generated from the schema, except for the
    # resourceRef object
    spec = schema["properties"].get("spec") or {}
    spec["properties"].pop("resourceRef", None)
    model_fields.update(build_object_fields(spec, "spec", parent_required=False))

    # Store the original schema on the model class
    model_fields["SCHEMA"] = schema

    meta_class = type("Meta", (), {"app_label": "crd_models"})
    model_fields["Meta"] = meta_class

    # create the model class
    model_name = kind
    model_class = type(model_name, (CRDModel,), model_fields)
    return model_class


def duplicate_field(field_name, model):
    field = model._meta.get_field(field_name)
    new_field = type(field).__new__(type(field))
    new_field.__dict__.update(field.__dict__)
    new_field.model = None
    new_field.auto_created = False
    return new_field


def build_object_fields(schema, name, verbose_name_prefix=None, parent_required=False):
    required_fields = schema.get("required") or []
    properties = schema.get("properties") or {}
    fields = {}

    for field_name, field_schema in properties.items():
        is_required = field_name in required_fields or parent_required
        full_name = f"{name}.{field_name}"
        result = get_django_field(
            field_schema,
            is_required,
            field_name,
            full_name,
            verbose_name_prefix=verbose_name_prefix,
        )
        if isinstance(result, dict):
            fields.update(result)
        else:
            fields[full_name] = result
    return fields


def get_django_field(
    field_schema, is_required, field_name, full_name, verbose_name_prefix=None
):
    field_type = field_schema.get("type") or "string"
    format = field_schema.get("format")
    verbose_name_prefix = verbose_name_prefix or ""
    verbose_name = f"{verbose_name_prefix} {deslugify(field_name)}".strip()

    # Pass down the requirement status from parent to child fields
    kwargs = {
        "blank": not is_required,  # All fields are optional by default
        "null": not is_required,
        "help_text": field_schema.get("description"),
        "validators": [],
        "verbose_name": verbose_name,
        "default": field_schema.get("default"),
    }

    if minimum := field_schema.get("minimum"):
        kwargs["validators"].append(MinValueValidator(minimum))
    if maximum := field_schema.get("maximum"):
        kwargs["validators"].append(MaxValueValidator(maximum))

    if field_type == "string":
        if format == "date-time":
            return models.DateTimeField(**kwargs)
        elif format == "date":
            return models.DateField(**kwargs)
        else:
            max_length = field_schema.get("max_length") or 255
            if pattern := field_schema.get("pattern"):
                kwargs["validators"].append(RegexValidator(regex=pattern))
            if choices := field_schema.get("enum"):
                kwargs["choices"] = ((choice, choice) for choice in choices)
            return models.CharField(max_length=max_length, **kwargs)
    elif field_type == "integer":
        return models.IntegerField(**kwargs)
    elif field_type == "number":
        return models.FloatField(**kwargs)
    elif field_type == "boolean":
        return models.BooleanField(**kwargs)
    elif field_type == "object":
        # Here we pass down the requirement status to nested objects
        return build_object_fields(
            field_schema,
            full_name,
            verbose_name_prefix=f"{verbose_name}:",
            parent_required=is_required,
        )
    elif field_type == "array":
        kwargs["help_text"] = field_schema.get("description") or _("List of values")
        field = models.JSONField(**kwargs)
        formfield_kwargs = {
            "label": field.verbose_name,
            "required": not field.blank,
        }

        array_validation = {}
        if min_items := field_schema.get("min_items"):
            array_validation["min_items"] = min_items
        if max_items := field_schema.get("max_items"):
            array_validation["max_items"] = max_items
        if unique_items := field_schema.get("unique_items"):
            array_validation["unique_items"] = unique_items
        if items_schema := field_schema.get("items"):
            array_validation["items_schema"] = items_schema
        if array_validation:
            formfield_kwargs["array_validation"] = array_validation

        field.formfield = lambda: DynamicArrayField(**formfield_kwargs)

        return field
    return models.CharField(max_length=255, **kwargs)


def unnest_data(data):
    result = {}

    def _flatten_dict(d, parent_key=""):
        for key, value in d.items():
            new_key = f"{parent_key}.{key}" if parent_key else key
            if isinstance(value, dict):
                _flatten_dict(value, new_key)
            else:
                result[new_key] = value

    _flatten_dict(data)
    return result
