#  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.
"""Unit tests for Debusine checks."""
import getpass
import os
import tempfile
from unittest import mock

from django.conf import settings
from django.core.checks import Error
from django.test import TestCase, override_settings

from debusine.project import checks


class ChecksTests(TestCase):
    """Tests for functions in checks.py."""

    def create_temporary_directory(self):
        """Create and return a temporary directory. Schedules deletion."""
        directory = tempfile.mkdtemp(prefix="debusine-settings-defaults-test")
        self.addCleanup(lambda: os.rmdir(directory))
        return directory

    @override_settings()
    def test_check_directories_owners_mandatory_dirs_ok(self):
        """_check_directories_owners with mandatory dirs return empty list."""
        settings.PATH_TO_DIR_1 = self.create_temporary_directory()
        settings.PATH_TO_DIR_2 = self.create_temporary_directory()
        self.assertEqual(
            checks._check_directories_owners(
                ["PATH_TO_DIR_1", "PATH_TO_DIR_2"], missing_ok=False
            ),
            [],
        )

    @override_settings()
    def test_check_directories_owners_mandatory_dirs_not_exist(self):
        """_check_directories_owners return error: mandatory dir not exist."""
        settings.PATH_TO_DIR = "/does/not/exist"
        self.assertEqual(
            checks._check_directories_owners(["PATH_TO_DIR"], missing_ok=False),
            [Error(f"{settings.PATH_TO_DIR} must exist")],
        )

    @override_settings()
    def test_check_directories_owners_mandatory_owner_mismatch(self):
        """_check_directory_owners return error: mandatory dir owner wrong."""
        settings.PATH_TO_DIR = self.create_temporary_directory()

        invalid_user = "an-invalid-user"

        patcher = mock.patch("pathlib.Path.owner")
        mocked_pathlib_owner = patcher.start()
        mocked_pathlib_owner.return_value = invalid_user
        self.addCleanup(patcher.stop)

        self.assertEqual(
            checks._check_directories_owners(["PATH_TO_DIR"], missing_ok=False),
            [
                Error(
                    f"{settings.PATH_TO_DIR} owner ({invalid_user}) must match "
                    f"current user ({getpass.getuser()})"
                )
            ],
        )

    @override_settings()
    def test_check_directories_owners_optional_owner_mismatch(self):
        """_check_directory_owners return error: optional dir owner wrong."""
        settings.PATH_TO_DIR = self.create_temporary_directory()

        invalid_user = "an-invalid-user"

        patcher = mock.patch("pathlib.Path.owner")
        mocked_pathlib_owner = patcher.start()
        mocked_pathlib_owner.return_value = invalid_user
        self.addCleanup(patcher.stop)

        self.assertEqual(
            checks._check_directories_owners(["PATH_TO_DIR"], missing_ok=True),
            [
                Error(
                    f"{settings.PATH_TO_DIR} owner ({invalid_user}) must match "
                    f"current user ({getpass.getuser()})"
                )
            ],
        )

    @override_settings()
    def test_check_directories_owners_missing_optional_dirs_ok(self):
        """_check_directories_owners with optional dirs return empty list."""
        settings.PATH_TO_DIR_1 = "/does/not/exist"
        settings.PATH_TO_DIR_2 = "/does/not/exist"
        self.assertEqual(
            checks._check_directories_owners(
                ["PATH_TO_DIR_1", "PATH_TO_DIR_2"], missing_ok=True
            ),
            [],
        )

    def test_mandatory_directories_check(self):
        """
        mandatory_directories_check uses check_directories_owners.

        mandatory_directories_check calls and returns
        _check_directories_owners()
        """
        patcher = mock.patch(
            "debusine.project.checks._check_directories_owners"
        )
        check_directories_mocked = patcher.start()
        check_directories_mocked.return_value = [Error("Some Error")]
        self.addCleanup(patcher.stop)

        self.assertEqual(
            checks.mandatory_directories_check(app_configs=None),
            check_directories_mocked.return_value,
        )

        check_directories_mocked.assert_called_with(
            ["DEBUSINE_DATA_PATH"], missing_ok=False
        )

    def test_optional_directories_check(self):
        """
        optional_directories_check uses check_directories_owners.

        optional_directories_check calls and returns
        _check_directories_owners()
        """
        patcher = mock.patch(
            "debusine.project.checks._check_directories_owners"
        )
        check_directories_mocked = patcher.start()
        check_directories_mocked.return_value = [Error("Some Error")]
        self.addCleanup(patcher.stop)

        self.assertEqual(
            checks.optional_directories_check(app_configs=None),
            check_directories_mocked.return_value,
        )

        check_directories_mocked.assert_called_with(
            [
                "STATIC_ROOT",
                "MEDIA_ROOT",
                "DEBUSINE_CACHE_DIRECTORY",
                "DEBUSINE_TEMPLATE_DIRECTORY",
            ],
            missing_ok=True,
        )

    @override_settings()
    def test_secret_key_not_default_in_debug_0(self):
        """secret_key_not_default_in_debug_0 return Errors or empty list."""
        failing_result = [
            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())'",
            )
        ]

        test_params = [
            {
                "TEST_MODE": False,
                "DEBUG": True,
                "SECRET_KEY": "some-key",
                "result": [],
            },
            {
                "TEST_MODE": False,
                "DEBUG": False,
                "SECRET_KEY": "some-key",
                "result": [],
            },
            {
                "TEST_MODE": False,
                "DEBUG": True,
                "SECRET_KEY": settings.SECRET_KEY,
                "result": [],
            },
            {
                "TEST_MODE": False,
                "DEBUG": False,
                "SECRET_KEY": settings.SECRET_KEY,
                "result": failing_result,
            },
            {
                "TEST_MODE": True,
                "DEBUG": False,
                "SECRET_KEY": settings.SECRET_KEY,
                "result": [],
            },
        ]

        for param in test_params:
            settings.TEST_MODE = param["TEST_MODE"]
            settings.DEBUG = param["DEBUG"]
            settings.SECRET_KEY = param["SECRET_KEY"]

            with self.subTest(params=param):
                self.assertEqual(
                    checks.secret_key_not_default_in_debug_0(app_configs=None),
                    param["result"],
                )

    @override_settings()
    def test_disable_automatic_scheduling_false_in_production(self):
        """Automatic scheduling is not disabled in production."""
        failing_result = [
            Error(
                "DISABLE_AUTOMATIC_SCHEDULING can only be enabled in TEST_MODE",
                hint="Remove DISABLE_AUTOMATIC_SCHEDULING=True from "
                "debusine-server settings",
            )
        ]

        test_params = [
            {
                "TEST_MODE": True,
                "DISABLE_AUTOMATIC_SCHEDULING": True,
                "result": [],
            },
            {
                "TEST_MODE": True,
                "DISABLE_AUTOMATIC_SCHEDULING": False,
                "result": [],
            },
            {
                "TEST_MODE": True,
                "DISABLE_AUTOMATIC_SCHEDULING": None,
                "result": [],
            },
            {
                "TEST_MODE": False,
                "DISABLE_AUTOMATIC_SCHEDULING": True,
                "result": failing_result,
            },
            {
                "TEST_MODE": False,
                "DISABLE_AUTOMATIC_SCHEDULING": False,
                "result": [],
            },
            {
                "TEST_MODE": False,
                "DISABLE_AUTOMATIC_SCHEDULING": None,
                "result": [],
            },
            {
                "TEST_MODE": None,
                "DISABLE_AUTOMATIC_SCHEDULING": True,
                "result": failing_result,
            },
            {
                "TEST_MODE": None,
                "DISABLE_AUTOMATIC_SCHEDULING": False,
                "result": [],
            },
            {
                "TEST_MODE": None,
                "DISABLE_AUTOMATIC_SCHEDULING": None,
                "result": [],
            },
        ]

        for param in test_params:
            with self.subTest(params=param):
                if (test_mode := param["TEST_MODE"]) is not None:
                    settings.TEST_MODE = test_mode

                if (
                    disable_automatic_scheduling := param[
                        "DISABLE_AUTOMATIC_SCHEDULING"
                    ]
                ) is not None:
                    settings.DISABLE_AUTOMATIC_SCHEDULING = (
                        disable_automatic_scheduling
                    )

                self.assertEqual(
                    checks.disable_automatic_scheduling_false_in_production(
                        app_configs=None
                    ),
                    param["result"],
                )
