# Inspired by https://gist.github.com/hakib/e2e50d41d19a6984dc63bd94580c8647

"""
Custom django checks.
    H001: Field has no verbose name.
    H002: Model must define class Meta.
    H003: Model must define db table name.
    H004: Model must define db table name comment.
"""
import ast
import inspect

import django.apps
from django.conf import settings
from django.core import checks
from django.core.exceptions import FieldDoesNotExist


def get_argument(node, arg):
    for kw in node.value.keywords:
        if kw.arg == arg:
            return kw
    return None


def check_model(model):
    """Check a single model.
    Yields (django.checks.CheckMessage)
    """
    model_source = inspect.getsource(model)
    model_node = ast.parse(model_source)
    assert isinstance(model_node, ast.Module)

    class_meta = None
    for node in model_node.body[0].body:  # type: ignore
        if isinstance(node, ast.ClassDef):
            # class Meta
            if node.name == "Meta":
                class_meta = node

        # Fields
        elif isinstance(node, ast.Assign):
            if len(node.targets) != 1:
                continue

            if not isinstance(node.targets[0], ast.Name):
                continue

            field_name = node.targets[0].id
            try:
                field = model._meta.get_field(field_name)
            except FieldDoesNotExist:
                continue

            verbose_name = get_argument(node, "verbose_name")
            if verbose_name is None:
                yield django.core.checks.Warning(
                    "Field has no verbose name",
                    hint="Set verbose name on the field.",
                    obj=field,
                    id="H001",
                )

    if class_meta is None:
        yield django.core.checks.Warning(
            'Model "{}" must define class Meta'.format(model._meta.model_name),
            hint='Add class Meta to model "{}".'.format(model._meta.model_name),
            obj=model,
            id="H002",
        )

    else:
        db_table = None
        db_table_comment = None

        for node in ast.iter_child_nodes(class_meta):
            if not isinstance(node, ast.Assign):
                continue

            if not isinstance(node.targets[0], ast.Name):
                continue

            attr = node.targets[0].id

            if attr == "db_table":
                db_table = node

            if attr == "db_table_comment":
                db_table_comment = node

        if db_table is None:
            yield django.core.checks.Warning(
                "Model has no database table name",
                hint="Add db_table to class Meta.",
                obj=model,
                id="H003",
            )

        if db_table_comment is None:
            yield django.core.checks.Warning(
                "Model has no database table name comment",
                hint="Add db_table_comment to class Meta.",
                obj=model,
                id="H004",
            )


@checks.register(django.core.checks.Tags.models)
def check_models(app_configs, **kwargs):
    errors = []
    for app in django.apps.apps.get_app_configs():
        # Skip third party apps.
        if app.path.find("site-packages") > -1:
            continue

        for model in app.get_models():
            # Skip all models that start with the string 'Historical'
            # since they are autogenerated by django simple history
            if model.__name__.startswith("Historical"):
                continue

            for check_message in check_model(model):
                errors.append(check_message)

    return errors


@checks.register()
def check_migrate_run(app_configs, **kwargs):
    errors = []
    # Only run this check for specific django db related commands
    if (
        settings.CURRENT_DJANGO_COMMAND in {"migrate", "makemigrations"}
        and settings.USE_CLOUD_SQL_AUTH_PROXY
    ):
        errors.append(
            checks.Error(
                "Cannot run database commands e.g. migrate or makemigrations against any cloud databases",
                hint=(
                    "Please make sure USE_CLOUD_SQL_AUTH_PROXY is set to False before"
                    "running any database commands"
                ),
            )
        )

    return errors
