"""Small tooling tests; never start a nested project regression."""
from contextlib import nullcontext
from io import StringIO
from unittest.mock import MagicMock, patch

from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import SimpleTestCase

from apps.reporting.management.commands import audit_project as audit


class AuditCommandTests(SimpleTestCase):
    def run_command(self, *args):
        with patch.object(audit, "call_command") as commands, \
                patch.object(audit.Command, "check_migrations"), \
                patch.object(audit.Command, "git", return_value=""), \
                patch.object(audit, "isolated_test_database", return_value=nullcontext()):
            call_command("audit_project", *args, stdout=StringIO(), stderr=StringIO())
        return commands

    def test_default_and_quick_run_only_smoke_tests(self):
        for args in [(), ("--quick",)]:
            with self.subTest(args=args):
                commands = self.run_command(*args)
                self.assertEqual(commands.call_args.args, ("test", *audit.SMOKE_TESTS))
                self.assertFalse(commands.call_args.kwargs["interactive"])
                drift = commands.call_args_list[1]
                self.assertTrue(drift.kwargs["check"])
                self.assertTrue(drift.kwargs["dry_run"])

    def test_full_uses_complete_discovery_once(self):
        commands = self.run_command("--full")
        self.assertEqual(commands.call_args.args, ("test",))
        self.assertEqual(sum(c.args[0] == "test" for c in commands.call_args_list), 1)

    def test_conflicting_modes_are_rejected(self):
        with self.assertRaises(CommandError):
            call_command("audit_project", quick=True, full=True, stdout=StringIO())

    def test_required_failures_propagate_and_other_checks_continue(self):
        for failing in ("check", "makemigrations", "test"):
            with self.subTest(failing=failing):
                def execute(name, *args, **kwargs):
                    if name == failing:
                        raise SystemExit(1)
                with patch.object(audit, "call_command", side_effect=execute) as commands, \
                        patch.object(audit.Command, "check_migrations"), \
                        patch.object(audit.Command, "git", return_value=""), \
                        patch.object(audit, "isolated_test_database", return_value=nullcontext()):
                    with self.assertRaises(CommandError):
                        call_command("audit_project", stdout=StringIO(), stderr=StringIO())
                self.assertEqual(commands.call_args.args[0], "test")

    def test_pending_migrations_fail_without_applying(self):
        with patch.object(audit, "MigrationExecutor") as executor:
            executor.return_value.loader.detect_conflicts.return_value = {}
            executor.return_value.migration_plan.return_value = [(object(), False)]
            with self.assertRaises(CommandError):
                audit.Command().check_migrations()
            executor.return_value.migrate.assert_not_called()

    def test_tracked_env_rejected_without_reading_contents(self):
        with patch.object(audit.Command, "git", return_value=".env\0.env.example\0"):
            with self.assertRaises(CommandError):
                audit.Command().check_env_tracking()

    def test_git_uses_read_only_arguments_without_shell(self):
        with patch.object(audit.subprocess, "run") as run:
            audit.Command().git("diff", "--check")
        self.assertEqual(run.call_args.args[0][-2:], ["diff", "--check"])
        self.assertFalse(run.call_args.kwargs["shell"])
        self.assertTrue(run.call_args.kwargs["check"])

    def test_isolation_restores_settings_even_after_failed_setup(self):
        connection = MagicMock()
        connection.settings_dict = {
            "ENGINE": "django.db.backends.postgresql", "NAME": "normal",
            "TEST": {"NAME": None, "MIGRATE": True},
        }
        with patch.object(audit, "connections", {"default": connection}):
            with self.assertRaises(RuntimeError):
                with audit.isolated_test_database():
                    generated = connection.settings_dict["TEST"]["NAME"]
                    self.assertTrue(generated.startswith("audit_"))
                    self.assertNotEqual(generated, "normal")
                    connection.settings_dict["NAME"] = generated
                    raise RuntimeError("failed setup")
        self.assertEqual(connection.settings_dict["NAME"], "normal")
        self.assertIsNone(connection.settings_dict["TEST"]["NAME"])
        connection.creation.destroy_test_db.assert_not_called()
