from typing import Any, Dict, Tuple

from django.apps.registry import apps

from django_scaffolding_tools.exceptions import DjangoParsingException


class DjangoAppManager:
    def __init__(self):
        self.installed_apps = dict(self.get_installed_apps())

    def get_app(self, app_name):
        return self.installed_apps.get(app_name)

    def get_installed_apps(self):
        for app_config in apps.get_app_configs():
            yield app_config.name, app_config

    def get_model(self, app_name, model_name):
        app = self.get_app(app_name)
        for model in app.get_models():
            if model_name == model.__name__:
                return model
        return None

    def get_project_apps(self, project_name):
        project_apps = dict()
        for app_name, app_config in self.installed_apps.items():
            app_project = app_name.split(".")[0]
            if app_project == project_name:
                project_apps[app_name] = app_config
        return project_apps

    def get_app_data(self, app_name):
        """Read application data converts into a dictionary

        :param app_name: Application name
        :return: Dictionary with application data
        """
        app = self.get_app(app_name)
        app_dict = dict()
        app_dict["app_name"] = app.name
        app_dict["models"] = dict()
        for key, model in app.models.items():
            app_dict["models"][key] = dict()
            app_dict["models"][key]["model_name"] = model.__name__
            app_dict["models"][key]["original_attrs"] = model._meta.original_attrs
            app_dict["models"][key]["fields"] = list()
            for field in model._meta.fields:
                self._convert_field_to_dict(field, app_dict["models"][key]["fields"])

        return app_dict

    def _convert_field_to_dict(self, field, field_list):
        field_dict = dict()
        field_dict["field_name"] = field.name
        field_dict["type"] = type(field).__name__
        field_dict["unique"] = field.unique
        field_dict["primary_key"] = field.primary_key
        field_dict["editable"] = field.editable
        if hasattr(field, "choices") and field.choices is not None:
            field_dict["choices_type"] = type(field.choices).__name__
            if field_dict["choices_type"] == "tuple":
                field_dict["choices"] = field.choices
        if hasattr(field, "max_length") and field.max_length is not None:
            field_dict["max_length"] = field.max_length
        if hasattr(field, "max_digits") and field.max_digits is not None:
            field_dict["max_digits"] = field.max_digits
        if hasattr(field, "decimal_places") and field.decimal_places is not None:
            field_dict["decimal_places"] = field.decimal_places
        if hasattr(field, "remote_field") and field.remote_field is not None:
            field_dict["remote_field"] = field.remote_field.model.__name__
        field_list.append(field_dict)
        return field_dict


def get_max_length(field_data: Dict[str, Any]) -> int:
    if field_data["data_type"] != "CharField":
        raise DjangoParsingException("Max length can only be used for CharField.")
    for keyword in field_data["keywords"]:
        if keyword.get("name") == "max_length":
            return keyword["value"]
    raise DjangoParsingException("No max length keyword found.")


def get_decimal_info(field_data: Dict[str, Any]) -> Tuple[int, int]:
    max_digits = 0
    decimal_places = 0
    if field_data["data_type"] != "DecimalField":
        raise DjangoParsingException("Not a decimal field.")
    for keyword in field_data["keywords"]:
        if keyword.get("name") == "max_digits":
            max_digits = keyword["value"]
        elif keyword.get("name") == "decimal_places":
            decimal_places = keyword["value"]
    return max_digits, decimal_places
