# Copyright 2022 The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Debusine checks using Django checks framework."""

import getpass
import os
import pathlib
import sys

from django.conf import settings
from django.core.checks import Error, register
from django.db import connections
from django.db.migrations.loader import MigrationLoader


def _check_directories_owners(setting_names, *, missing_ok):
    current_user = getpass.getuser()

    errors = []

    for setting_name in setting_names:
        directory = pathlib.Path(getattr(settings, setting_name))

        if not directory.exists() and missing_ok:
            continue
        elif not directory.exists():
            errors.append(Error(f"{directory} must exist"))
        elif directory.owner() != current_user:
            errors.append(
                Error(
                    f"{directory} owner ({directory.owner()}) must match "
                    f"current user ({current_user})"
                )
            )

    return errors


@register()
def directories_owners_check(app_configs, **kwargs):  # noqa: U100
    """Check optional directories, if they exist, have the correct owner."""
    if _is_migration_command():
        return []

    return _check_directories_owners(
        [
            "DEBUSINE_DATA_PATH",
            "STATIC_ROOT",
            "MEDIA_ROOT",
            "DEBUSINE_CACHE_DIRECTORY",
            "DEBUSINE_TEMPLATE_DIRECTORY",
            "DEBUSINE_UPLOAD_DIRECTORY",
            "DEBUSINE_STORE_DIRECTORY",
        ],
        missing_ok=True,
    )


@register()
def secret_key_not_default_in_debug_0(app_configs, **kwargs):  # noqa: U100
    """Check SECRET_KEY is not the default if Debug=False (unless testing)."""
    if getattr(settings, "TEST_MODE", False):
        # In Django the tests run with settings.DEBUG = False by design.
        # When testing Debusine it is allowed to use the default SECRET_KEY
        return []

    if not settings.DEBUG and settings.SECRET_KEY.startswith("default:"):
        return [
            Error(
                'Default SECRET_KEY cannot be used in DEBUG=False. Make sure '
                'to use "SECRET_KEY = read_secret_key(path)" from selected.py '
                'settings file',
                hint="Generate a secret key using the command: "
                "$ python3 -c 'from django.core.management.utils import "
                "get_random_secret_key; print(get_random_secret_key())'",
            )
        ]

    return []


@register()
def disable_automatic_scheduling_false_in_production(
    app_configs, **kwargs  # noqa: U100
):
    """Check DISABLE_AUTOMATIC_SCHEDULING is enabled only in TEST_MODE."""
    if getattr(settings, "DISABLE_AUTOMATIC_SCHEDULING", False) and not getattr(
        settings, "TEST_MODE", False
    ):
        return [
            Error(
                "DISABLE_AUTOMATIC_SCHEDULING can only be enabled in TEST_MODE",
                hint="Remove DISABLE_AUTOMATIC_SCHEDULING=True "
                "from debusine-server settings",
            )
        ]

    return []


_migration_commands = (
    "makemigrations",
    "migrate",
    "showmigrations",
    "sqlmigrate",
    "squashmigrations",
)


def _is_migration_command():
    return sys.argv[1] in _migration_commands


@register()
def all_migrations_applied(app_configs, **kwargs):  # noqa: U100
    """
    Check that all migrations have been applied.

    Avoid checking if any migration related command is being ran
    """
    if _is_migration_command():
        return []

    loader = MigrationLoader(connection=connections["default"], load=True)

    applied_migrations = loader.applied_migrations.keys()
    disk_migrations = loader.graph.nodes.keys()

    to_apply_migrations = sorted(disk_migrations - applied_migrations)

    errors = []

    for migration in to_apply_migrations:
        errors.append(
            Error(
                f"Non-applied migration: {migration[0]} {migration[1]}",
                hint='Run "debusine-admin migrate"',
            )
        )

    return errors


@register()
def local_file_store_directories_writable(app_configs, **kwargs):  # noqa: U100
    """
    Check that each local file store base_directory exist.

    Avoid checking it if a migration command is being ran
    """
    if _is_migration_command():
        return []

    from debusine.db.models import FileStore

    errors = []

    for file_store in FileStore.objects.filter(
        backend=FileStore.BackendChoices.LOCAL
    ).order_by("name"):
        file_store_backend = file_store.get_backend_object()
        base_directory = file_store_backend.base_directory()
        if not os.access(base_directory, os.W_OK):
            errors.append(
                Error(
                    f'FileStore "{file_store.name}" has a non-existent '
                    f'or non-writable base_directory: '
                    f'"{str(file_store_backend.base_directory())}"',
                    hint="Create the directory, change the permissions "
                    "or change the FileStore configuration",
                )
            )

    return errors
