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

"""Debusine command line interface."""

import argparse
import signal
import types
from typing import NoReturn

import argcomplete

from debusine.client.commands import Command
from debusine.client.config import ConfigHandler
from debusine.utils import debusine_version


class Cli:
    """
    Entry point for the command line debusine client.

    Usage:
        main = Cli(sys.argv[1:]) # [1:] to exclude the script name
        main.execute()
    """

    def __init__(self, argv: list[str]) -> None:
        """Initialize object."""
        self._argv = argv

    @staticmethod
    def _exit(signum: int, frame: types.FrameType | None) -> NoReturn:
        signum, frame  # fake usage for vulture
        raise SystemExit(0)

    @classmethod
    def build_argument_parser(cls) -> argparse.ArgumentParser:
        """Build the argument parser."""
        parser = argparse.ArgumentParser(
            prog='debusine',
            description='Interacts with a Debusine server.',
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        )
        parser.add_argument(
            "--version",
            action="version",
            version=f"%(prog)s {debusine_version() or 'uninstalled-version'}",
        )

        parser.add_argument(
            '--server',
            help=(
                'Set server to be used, either by section name or as '
                'FQDN/scope (use configuration file default if not specified)'
            ),
        )

        parser.add_argument(
            '--scope',
            help=(
                'Set scope to be used (use configuration file default '
                'if not specified)'
            ),
        )

        parser.add_argument(
            '--config-file',
            default=ConfigHandler.DEFAULT_CONFIG_FILE_PATH,
            help='Config file path',
        )

        parser.add_argument("-s", "--silent", action="store_true")

        parser.add_argument(
            "-d", "--debug", action="store_true", help='Print HTTP traffic'
        )

        subparsers = parser.add_subparsers(
            help='Sub command', dest='sub-command', required=True
        )

        Command.add_subparser(
            subparsers,
            "workspace",
            argument_help="Workspace management",
            subparser_help="Workspace management subcommands",
        )
        collection_parser = subparsers.add_parser(
            "collection", help="Collection management"
        )
        collection_subparsers = collection_parser.add_subparsers(
            help="Collection management subcommands", required=True
        )
        Command.add_parsers(collection_subparsers, "collection")

        Command.add_subparser(
            collection_subparsers,
            "collection.relation",
            argument="relation",
            argument_help="Collection relation management",
            subparser_help="Collection relation management subcommands",
        )

        Command.add_subparser(
            subparsers,
            "workflow-template",
            argument_help="Workflow template management",
            subparser_help="Workflow template management subcommands",
        )
        Command.add_subparser(
            subparsers,
            "workflow",
            argument_help="Workflow management",
            subparser_help="Workflow management subcommands",
        )
        Command.add_subparser(
            subparsers,
            "work-request",
            argument_help="Work request management",
            subparser_help="Work request management subcommands",
        )
        Command.add_subparser(
            subparsers,
            "artifact",
            argument_help="Artifact management",
            subparser_help="Artifact management subcommands",
        )
        Command.add_subparser(
            subparsers,
            "asset",
            argument_help="Asset management",
            subparser_help="Asset management subcommands",
        )
        Command.add_subparser(
            subparsers,
            "task-config",
            argument_help="Task configuration management",
            subparser_help="Task configuration management subcommands",
        )
        archive_parser = subparsers.add_parser(
            "archive", help="Archive management"
        )
        archive_subparsers = archive_parser.add_subparsers(
            help="Archive management subcommands", required=True
        )
        Command.add_subparser(
            archive_subparsers,
            "archive.suite",
            argument="suite",
            argument_help="Suite management",
            subparser_help="Suite management subcommands",
        )
        Command.add_subparser(
            archive_subparsers,
            "archive.token",
            argument="token",
            argument_help="Token management",
            subparser_help="Token management subcommands",
        )

        # Add at the end so that the "object verb" format command groups show
        # up first in the help output
        Command.add_parsers(subparsers)

        argcomplete.autocomplete(parser)
        return parser

    def _parse_args(self) -> None:
        """Parse argv and store results in self.args."""
        self.parser = self.build_argument_parser()
        self.args = self.parser.parse_args(self._argv)

    def get_command(self) -> Command:
        """Instantiate the Command selected by the user."""
        self._parse_args()

        if command := Command.create(self.parser, self.args):
            return command

        sub_command = getattr(self.args, 'sub-command')
        raise AssertionError(f"Unexpected sub-command name: {sub_command}")

    def execute(self) -> None:
        """Execute the command requested by the user."""
        signal.signal(signal.SIGINT, self._exit)
        signal.signal(signal.SIGTERM, self._exit)

        command = self.get_command()
        command.run_porcelain()
