"""
These functions handle the adding of fields to other models.
"""

from typing import Optional, Type, Union

from collections.abc import Iterable

from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.db.models import CASCADE, ForeignKey, ManyToManyField

# from settings import self._field_registry, self._model_registry
from django.utils.translation import gettext_lazy as _

from . import fields

FIELD_TYPES = {
    "ForeignKey": ForeignKey,
    "ManyToManyField": ManyToManyField,
}


class Registry(object):
    """Keeps track of fields and models registered."""

    def __init__(self):
        self._field_registry = {}
        self._model_registry = {}

    def register_model(self, app: str, model_name, field_type: str, field_definitions: Union[str, Iterable]):
        """
        Registration process for Django 1.7+.

        Args:
            app: app name/label
            model_name: name of the model
            field_definitions: a string, tuple or list of field configurations
            field_type: either 'ForeignKey' or 'ManyToManyField'

        Raises:
            ImproperlyConfigured: For incorrect parameter types or missing model.
        """
        from django.apps import apps

        app_label = app

        if isinstance(field_definitions, str):
            field_definitions = [field_definitions]
        elif not isinstance(field_definitions, Iterable):
            raise ImproperlyConfigured(
                _("Field configuration for %(app)s should be a string or iterable") % {"app": app}
            )

        if field_type not in ("ForeignKey", "ManyToManyField"):
            raise ImproperlyConfigured(_('`field_type` must be either `"ForeignKey"` or `"ManyToManyField"`.'))

        try:
            if not hasattr(model_name, "_meta"):
                app_config = apps.get_app_config(app)
                app_label = app_config.label
                model = app_config.get_model(model_name)
            else:
                model = model_name
                model_name = model._meta.model_name
            opts = model._meta
            if app_label not in self._model_registry:
                self._model_registry[app_label] = []
            if model not in self._model_registry[app_label]:
                self._model_registry[app_label].append(model)
        except LookupError:
            raise ImproperlyConfigured(
                'Model "%(model)s" doesn\'t exist in app "%(app)s".' % {"model": model_name, "app": app}
            )

        if not isinstance(field_definitions, (tuple, list)):
            field_definitions = [field_definitions]

        for fld in field_definitions:
            extra_params = {"to": "categories.Category", "blank": True}
            if field_type != "ManyToManyField":
                extra_params["on_delete"] = CASCADE
                extra_params["null"] = True
            if isinstance(fld, str):
                field_name = fld
            elif isinstance(fld, dict):
                if "name" in fld:
                    field_name = fld.pop("name")
                else:
                    continue
                extra_params.update(fld)
            else:
                raise ImproperlyConfigured(
                    _("%(settings)s doesn't recognize the value of %(app)s.%(model)s")
                    % {"settings": "CATEGORY_SETTINGS", "app": app, "model": model_name}
                )
            registry_name = ".".join([app_label, model_name.lower(), field_name])
            if registry_name in self._field_registry:
                continue

            try:
                opts.get_field(field_name)
            except FieldDoesNotExist:
                self._field_registry[registry_name] = FIELD_TYPES[field_type](**extra_params)
                self._field_registry[registry_name].contribute_to_class(model, field_name)

    def register_m2m(self, model, field_name: str = "categories", extra_params: Optional[dict] = None):
        """Register a field name to the model as a many to many field."""
        extra_params = extra_params or {}
        return self._register(model, field_name, extra_params, fields.CategoryM2MField)

    def register_fk(self, model, field_name: str = "category", extra_params: Optional[dict] = None):
        """Register a field name to the model as a foreign key."""
        extra_params = extra_params or {}
        return self._register(model, field_name, extra_params, fields.CategoryFKField)

    def _register(
        self,
        model,
        field_name: str,
        extra_params: Optional[dict] = None,
        field: Type = fields.CategoryFKField,
    ):
        """Does the heavy lifting for registering a field to a model."""
        app_label = model._meta.app_label
        extra_params = extra_params or {}
        registry_name = ".".join((app_label, model.__name__, field_name)).lower()

        if registry_name in self._field_registry:
            return  # raise AlreadyRegistered
        opts = model._meta
        try:
            opts.get_field(field_name)
        except FieldDoesNotExist:
            if app_label not in self._model_registry:
                self._model_registry[app_label] = []
            if model not in self._model_registry[app_label]:
                self._model_registry[app_label].append(model)
            self._field_registry[registry_name] = field(**extra_params)
            self._field_registry[registry_name].contribute_to_class(model, field_name)


registry = Registry()


def _process_registry(registry, call_func):
    """
    Given a dictionary, and a registration function, process the registry.
    """
    from django.apps import apps
    from django.core.exceptions import ImproperlyConfigured

    for key, value in list(registry.items()):
        model = apps.get_model(*key.split("."))
        if model is None:
            raise ImproperlyConfigured(_("%(key)s is not a model") % {"key": key})
        if isinstance(value, (tuple, list)):
            for item in value:
                if isinstance(item, str):
                    call_func(model, item)
                elif isinstance(item, dict):
                    field_name = item.pop("name")
                    call_func(model, field_name, extra_params=item)
                else:
                    raise ImproperlyConfigured(
                        _("%(settings)s doesn't recognize the value of %(key)s")
                        % {"settings": "CATEGORY_SETTINGS", "key": key}
                    )
        elif isinstance(value, str):
            call_func(model, value)
        elif isinstance(value, dict):
            field_name = value.pop("name")
            call_func(model, field_name, extra_params=value)
        else:
            raise ImproperlyConfigured(
                _("%(settings)s doesn't recognize the value of %(key)s")
                % {"settings": "CATEGORY_SETTINGS", "key": key}
            )
