# Copyright © 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 debusine Cli class."""

import contextlib
import io
import signal
from unittest import mock

from debusine.client.cli import Cli
from debusine.client.config import ConfigHandler
from debusine.client.tests.base import BaseCliTests


class CliTests(BaseCliTests):
    """Tests for the debusine command line interface generic functionality."""

    def test_client_invalid_subcommand(self) -> None:
        cli = self.create_cli(["does-not-exist"], create_config=False)
        stderr = io.StringIO()

        with contextlib.redirect_stderr(stderr), self.assertRaisesSystemExit(2):
            # Argparse prints help and exits with exit_code=2
            cli.execute()

        self.assertRegex(
            stderr.getvalue(),
            r"debusine: error: argument SUBCOMMAND: invalid choice:"
            r" 'does-not-exist' \(choose from",
        )

    def test_client_subcommand_not_handled(self) -> None:
        cli = self.create_cli(["artifact", "show", "1"], create_config=False)
        with (
            mock.patch(
                "debusine.client.commands.base.Command.create",
                return_value=None,
            ),
            self.assertRaisesRegex(
                AssertionError, "Unexpected sub-command name: artifact"
            ),
        ):
            cli.execute()

    def test_client_without_parameters(self) -> None:
        """
        Executing the client without any parameter returns an error.

        At least one subcommands is required. argparse prints help
        and exit.
        """
        cli = self.create_cli([], create_config=False)
        stderr = io.StringIO()

        with contextlib.redirect_stderr(stderr), self.assertRaisesSystemExit(2):
            # Argparse prints help and exits with exit_code=2
            cli.execute()

        self.assertGreater(len(stderr.getvalue()), 80)

    def test_client_help_include_default_setting(self) -> None:
        """Cli.execute() help include ConfigHandler.DEFAULT_CONFIG_FILE_PATH."""
        stdout = io.StringIO()

        cli = self.create_cli(['--help'], create_config=False)

        with contextlib.redirect_stdout(stdout), self.assertRaisesSystemExit(0):
            # Argparse prints help and exits with exit_code=0
            cli.execute()

        # argparse might add \n and spaces (for indentation) in the
        # output to align the text. In this case
        # ConfigHandler.DEFAULT_CONFIG_FILE_PATH could not be found
        output = stdout.getvalue().replace('\n', '').replace(' ', '')

        self.assertIn(
            str(ConfigHandler.DEFAULT_CONFIG_FILE_PATH).replace(' ', ''), output
        )

    def test_client_version(self) -> None:
        stdout = io.StringIO()
        cli = self.create_cli(["--version"], create_config=False)

        with (
            contextlib.redirect_stdout(stdout),
            self.assertRaisesSystemExit(0),
            mock.patch(
                "debusine.client.cli.debusine_version", return_value="0.1.0"
            ),
        ):
            cli.execute()

        self.assertEqual("debusine 0.1.0\n", stdout.getvalue())

    def test_client_version_uninstalled(self) -> None:
        stdout = io.StringIO()
        cli = self.create_cli(["--version"], create_config=False)

        with (
            contextlib.redirect_stdout(stdout),
            self.assertRaisesSystemExit(0),
            mock.patch(
                "debusine.client.cli.debusine_version", return_value=None
            ),
        ):
            cli.execute()

        self.assertEqual("debusine uninstalled-version\n", stdout.getvalue())

    def test_exit_raise_system_exit_0(self) -> None:
        """Cli._exit raise SystemExit(0)."""
        with self.assertRaisesSystemExit(0):
            Cli._exit(signal.SIGINT, None)

    def test_signal_handlers_set_on_exec(self) -> None:
        """Test that the signal handlers are set on exec()."""
        cli = self.create_cli(["show-work-request", "1"])

        # Cli.__init__() is not changing the signal handlers for SIG{INT,TERM}
        self.assertEqual(
            signal.getsignal(signal.SIGINT), self.default_sigint_handler
        )
        self.assertEqual(
            signal.getsignal(signal.SIGTERM), self.default_sigterm_handler
        )

        with mock.patch(
            "debusine.client.commands.work_requests.Show.run",
            autospec=True,
        ):
            cli.execute()

        # cli.execute() changed the signal handlers for SIG{INT,TERM}
        self.assertEqual(signal.getsignal(signal.SIGINT), cli._exit)
        self.assertEqual(signal.getsignal(signal.SIGTERM), cli._exit)
