# Copyright 2024 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.

"""Tests for the management command rename_scope."""
from typing import ClassVar
from unittest import mock

from django.core.management import CommandError

from debusine.db.models import Scope
from debusine.django.management.tests import call_command
from debusine.server.management.commands.scope import Command
from debusine.test.django import TestCase


class RenameScopeCommandTests(TestCase):
    """Tests for rename_scope management command."""

    scope1: ClassVar[Scope]
    scope2: ClassVar[Scope]

    @classmethod
    def setUpTestData(cls) -> None:
        """Set up common test data."""
        super().setUpTestData()
        cls.scope1 = cls.playground.get_or_create_scope(name="scope1")
        cls.scope2 = cls.playground.get_or_create_scope(name="scope2")

    def test_rename(self):
        """Test a successful rename."""
        stdout, stderr, exit_code = call_command(
            "scope", "rename", "scope1", "newname"
        )
        self.assertEqual(stdout, "")
        self.assertEqual(stderr, "")
        self.assertEqual(exit_code, 0)
        self.scope1.refresh_from_db()
        self.assertEqual(self.scope1.name, "newname")

    def test_noop(self):
        """Test renaming to current name."""
        with mock.patch("debusine.db.models.Scope.save") as save:
            stdout, stderr, exit_code = call_command(
                "scope", "rename", "scope1", "scope1"
            )
        self.assertEqual(stdout, "")
        self.assertEqual(stderr, "")
        self.assertEqual(exit_code, 0)

        save.assert_not_called()

    def test_source_does_not_exist(self):
        """Test renaming a nonexisting scope."""
        with self.assertRaisesRegex(
            CommandError, r"Scope 'does-not-exist' not found"
        ) as exc:
            call_command("scope", "rename", "does-not-exist", "newname")

        self.assertEqual(exc.exception.returncode, 3)

    def test_target_exists(self):
        """Test renaming with a name already in use."""
        stdout, stderr, exit_code = call_command(
            "scope", "rename", "scope1", "scope2"
        )
        self.assertEqual(stdout, "")
        self.assertEqual(
            stderr.splitlines(),
            [
                "Renamed scope would be invalid:",
                "* name: Scope with this Name already exists.",
            ],
        )

        self.assertEqual(exit_code, 3)
        self.scope1.refresh_from_db()
        self.assertEqual(self.scope1.name, "scope1")

    def test_new_name_invalid(self):
        """Test renaming to an invalid name."""
        stdout, stderr, exit_code = call_command(
            "scope", "rename", "scope1", "api"
        )
        self.assertEqual(stdout, "")
        self.assertEqual(
            stderr.splitlines(),
            [
                "Renamed scope would be invalid:",
                "* name: 'api' is not a valid scope name",
            ],
        )
        self.assertEqual(exit_code, 3)
        self.scope1.refresh_from_db()
        self.assertEqual(self.scope1.name, "scope1")

    def test_invalid_action(self):
        """Test invoking an invalid subcommand."""
        with self.assertRaisesRegex(
            CommandError, r"invalid choice: 'does-not-exist'"
        ) as exc:
            call_command("scope", "does-not-exist")

        self.assertEqual(exc.exception.returncode, 1)

    def test_unexpected_action(self):
        """Test a subcommand with no implementation."""
        command = Command()

        with self.assertRaisesRegex(
            CommandError, r"Action 'does_not_exist' not found"
        ) as exc:
            command.handle(action="does_not_exist")

        self.assertEqual(exc.exception.returncode, 3)
