"""CLI dispatcher — builds parser trees from command dataclasses."""

from __future__ import annotations

import logging
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
from typing import Any, Dict, List, Optional, Type

from byteb4rb1e.utils.argparse.command import CLICommand


class CLI:
    """Composable CLI built from a tree of Command dataclasses.

    Recursively bootstraps an argparse parser hierarchy and tracks
    dest names so ``run()`` can dispatch to the correct leaf command
    without dest chaining in the caller.

    Usage::

        cli = CLI(prog="repository", description="...")
        cli.bootstrap([MirrorCommand, IndexCommand])
        cli.run()
    """

    def __init__(
        self,
        prog: Optional[str] = None,
        description: str = "",
    ) -> None:
        kwargs = {}  # type: Dict[str, Any]
        if prog:
            kwargs["prog"] = prog
        if description:
            kwargs["description"] = description
        kwargs.setdefault(
            "formatter_class", ArgumentDefaultsHelpFormatter,
        )
        self.parser = ArgumentParser(**kwargs)
        self._dests = []  # type: List[str]
        self._commands = {}  # type: Dict[str, Command]

    def add_arguments(self, parser: ArgumentParser) -> None:
        """Add global arguments to the root parser."""
        parser.add_argument(
            "-v", "--verbose", action="count", default=0,
            help="Increase verbosity (-v for INFO, -vv for DEBUG)",
        )

    def bootstrap(
        self,
        commands: List[Type[Command]],
    ) -> None:
        """Build the parser tree from a list of top-level commands."""
        self.add_arguments(self.parser)
        dest = "command"
        self._dests.append(dest)
        sub = self.parser.add_subparsers(dest=dest)
        for cmd_cls in commands:
            self._add(sub, cmd_cls, prefix="")

    def _add(
        self,
        subparsers: Any,
        cmd_cls: Type[Command],
        prefix: str,
    ) -> None:
        """Recursively add a command and its subcommands."""
        cmd = cmd_cls()
        parser = subparsers.add_parser(
            cmd.name,
            formatter_class=ArgumentDefaultsHelpFormatter,
            **cmd.parser_kwargs(),
        )
        cmd.add_arguments(parser)

        key = "%s.%s" % (prefix, cmd.name) if prefix else cmd.name
        self._commands[key] = cmd

        if cmd._subcommands:
            dest = "%s_command" % cmd.name
            self._dests.append(dest)
            child_sub = parser.add_subparsers(dest=dest)
            for sc_cls in cmd._subcommands:
                self._add(child_sub, sc_cls, prefix=key)

    def _resolve(self, args: Any) -> Optional[Command]:
        """Walk dest chain to find the leaf command."""
        parts = []  # type: List[str]
        for dest in self._dests:
            val = getattr(args, dest, None)
            if val is None:
                continue
            parts.append(val)
        if not parts:
            return None
        key = ".".join(parts)
        return self._commands.get(key)

    @staticmethod
    def _setup_logging(verbosity: int) -> None:
        if verbosity >= 2:
            level = logging.DEBUG
        elif verbosity >= 1:
            level = logging.INFO
        else:
            level = logging.WARNING
        logging.basicConfig(
            level=level,
            format="%(asctime)s [%(levelname)s] %(message)s",
            handlers=[logging.StreamHandler()],
        )

    def run(self) -> None:
        """Parse args and dispatch to the leaf command."""
        args = self.parser.parse_args()
        self._setup_logging(getattr(args, "verbose", 0))
        cmd = self._resolve(args)
        if cmd is None:
            self.parser.print_help()
            raise SystemExit(1)
        raise SystemExit(cmd.execute(args))
