import re

from django.conf import settings
from django.core.checks import Error, register, Tags, Warning  # pylint: disable=redefined-builtin
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.db import connections

E002 = Error(
    "'nautobot.core.authentication.ObjectPermissionBackend' must be included in AUTHENTICATION_BACKENDS",
    id="nautobot.core.E002",
    obj=settings,
)

E003 = Error(
    "RELEASE_CHECK_TIMEOUT must be at least 3600 seconds (1 hour)",
    id="nautobot.core.E003",
    obj=settings,
)

E004 = Error(
    "RELEASE_CHECK_URL must be a valid API URL. Example: https://api.github.com/repos/nautobot/nautobot",
    id="nautobot.core.E004",
    obj=settings,
)

E005 = Error(
    "MAINTENANCE_MODE has been set but SESSION_ENGINE is still using the database.  Nautobot can not enter Maintenance mode.",
    id="nautobot.core.E005",
    obj=settings,
)

# E006 is reserved for use in v3.x.

# E007 and E008 are dynamically constructed inline below

E009 = Error(
    "You appear to have overridden settings.STORAGES incorrectly. "
    "In addition to the Django standard keys of 'default' and 'staticfiles', "
    "Nautobot also requires the key 'nautobotjobfiles' to define storage configuration for Job input/output files.",
    hint='Nautobot defaults to setting STORAGES["nautobotjobfiles"]["BACKEND"] to '
    '"db_file_storage.storage.DatabaseFileStorage", but in many cases you should override this. '
    "Refer to https://docs.nautobot.com/projects/core/en/stable/user-guide/administration/configuration/settings/#storages for guidance.",
    id="nautobot.core.E009",
    obj=settings,
)

W005 = Warning(
    "STORAGE_CONFIG has been set but STORAGE_BACKEND is not defined. STORAGE_CONFIG will be ignored.",
    id="nautobot.core.W005",
    obj=settings,
)

# W006 and W007 are reserved for use in v3.x.

# W008 is dynamically constructed inline below

MIN_POSTGRESQL_MAJOR_VERSION = 12
MIN_POSTGRESQL_MINOR_VERSION = 0

MIN_POSTGRESQL_VERSION = MIN_POSTGRESQL_MAJOR_VERSION * 10000 + MIN_POSTGRESQL_MINOR_VERSION


@register(Tags.security)
def check_object_permissions_backend(app_configs, **kwargs):
    if "nautobot.core.authentication.ObjectPermissionBackend" not in settings.AUTHENTICATION_BACKENDS:
        return [E002]
    return []


@register(Tags.compatibility)
def check_release_check_timeout(app_configs, **kwargs):
    if hasattr(settings, "RELEASE_CHECK_TIMEOUT") and settings.RELEASE_CHECK_TIMEOUT < 3600:
        return [E003]
    return []


@register(Tags.compatibility)
def check_release_check_url(app_configs, **kwargs):
    validator = URLValidator()
    if hasattr(settings, "RELEASE_CHECK_URL") and settings.RELEASE_CHECK_URL:
        try:
            validator(settings.RELEASE_CHECK_URL)
        except ValidationError:
            return [E004]
    return []


@register(Tags.compatibility)
def check_storage_config_and_backend(app_configs, **kwargs):
    if getattr(settings, "STORAGE_CONFIG", None) and not getattr(settings, "STORAGE_BACKEND", None):
        return [W005]
    return []


@register(Tags.compatibility)
def check_maintenance_mode(app_configs, **kwargs):
    if settings.MAINTENANCE_MODE and settings.SESSION_ENGINE == "django.contrib.sessions.backends.db":
        return [E005]
    return []


@register(Tags.database)
def check_postgresql_version(app_configs, databases=None, **kwargs):
    if databases is None:
        return []
    errors = []
    for alias in databases:
        conn = connections[alias]
        if conn.vendor == "postgresql":
            server_version = conn.cursor().connection.info.server_version
            if server_version < MIN_POSTGRESQL_VERSION:
                errors.append(
                    Error(
                        f"PostgreSQL version less than {MIN_POSTGRESQL_VERSION} "
                        f"(i.e. {MIN_POSTGRESQL_MAJOR_VERSION}.{MIN_POSTGRESQL_MINOR_VERSION}) "
                        "is not supported by this version of Nautobot",
                        id="nautobot.core.E006",
                        obj=f"connections[{alias}]",
                        hint=f"Detected version is {server_version} (major version {server_version // 10000})",
                    )
                )

    return errors


@register(Tags.security)
def check_sanitizer_patterns(app_configs, **kwargs):
    errors = []
    for entry in settings.SANITIZER_PATTERNS:
        if (
            not isinstance(entry, (tuple, list))
            or len(entry) != 2
            or not isinstance(entry[0], re.Pattern)
            or not isinstance(entry[1], str)
        ):
            errors.append(
                Error(
                    "Invalid entry in settings.SANITIZER_PATTERNS",
                    hint="Each entry must be a list or tuple of (compiled regexp, replacement string)",
                    obj=entry,
                    id="nautobot.core.E007",
                )
            )
            continue

        sanitizer, repl = entry
        try:
            sanitizer.sub(repl.format(replacement="(REDACTED)"), "Hello world!")
        except re.error as exc:
            errors.append(
                Error(
                    "Entry in settings.SANITIZER_PATTERNS not usable for sanitization",
                    hint=str(exc),
                    obj=entry,
                    id="nautobot.core.E008",
                )
            )

    return errors


@register(Tags.compatibility)
def check_storages_includes_nautobotjobfiles(app_configs, **kwargs):
    if "nautobotjobfiles" not in settings.STORAGES:
        return [E009]
    return []


@register(Tags.compatibility)
def check_for_deprecated_storage_settings(app_configs, **kwargs):
    """Warn if any deprecated storage settings are set."""
    warnings = []
    for setting_name, replacement in [
        ("DEFAULT_FILE_STORAGE", 'STORAGES["default"]["BACKEND"]'),
        ("JOB_FILE_IO_STORAGE", 'STORAGES["nautobotjobfiles"]["BACKEND"]'),
        ("STATICFILES_STORAGE", 'STORAGES["staticfiles"]["BACKEND"]'),
        ("STORAGE_CONFIG", 'STORAGES[...]["OPTIONS"]'),
        ("STORAGE_BACKEND", 'STORAGES["default"]["BACKEND"]'),
    ]:
        if settings.is_overridden(setting_name):
            warnings.append(
                Warning(
                    msg=f"The setting {setting_name} is deprecated since Nautobot v2.4.24, "
                    "and support will be removed in Nautobot 3.1.",
                    hint=f"You should migrate to setting {replacement} instead. Refer to "
                    "https://docs.nautobot.com/projects/core/en/stable/user-guide/administration/configuration/settings/#storages for guidance.",
                    obj=settings,
                    id="nautobot.core.W008",
                )
            )

    return warnings
