import shutil

from argparse import ArgumentParser
from asyncio import run
from inspect import signature
from typing import Callable

from sanic import Sanic
from sanic.application.logo import get_logo
from sanic.cli.base import (
    SanicArgumentParser,
    SanicHelpFormatter,
)


def make_executor_parser(parser: ArgumentParser) -> None:
    parser.add_argument(
        "command",
        help="Command to execute",
    )


class ExecutorSubParser(ArgumentParser):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.description:
            self.description = ""
        self.description = get_logo(True) + self.description


class Executor:
    def __init__(self, app: Sanic, kwargs: dict) -> None:
        self.app = app
        self.kwargs = kwargs
        self.commands = self._make_commands()
        self.parser = self._make_parser()

    def run(self, command: str, args: list[str]) -> None:
        if command == "exec":
            args = ["--help"]
        parsed_args = self.parser.parse_args(args)
        if command not in self.commands:
            raise ValueError(f"Unknown command: {command}")
        parsed_kwargs = vars(parsed_args)
        parsed_kwargs.pop("command")
        run(self.commands[command](**parsed_kwargs))

    def _make_commands(self) -> dict[str, Callable]:
        commands = {c.name: c.func for c in self.app._future_commands}
        return commands

    def _make_parser(self) -> SanicArgumentParser:
        width = shutil.get_terminal_size().columns
        parser = SanicArgumentParser(
            prog="sanic",
            description=get_logo(True),
            formatter_class=lambda prog: SanicHelpFormatter(
                prog,
                max_help_position=36 if width > 96 else 24,
                indent_increment=4,
                width=None,
            ),
        )

        subparsers = parser.add_subparsers(
            dest="command",
            title="  Commands",
            parser_class=ExecutorSubParser,
        )
        for command in self.app._future_commands:
            sub = subparsers.add_parser(
                command.name,
                help=command.func.__doc__ or f"Execute {command.name}",
                formatter_class=SanicHelpFormatter,
            )
            self._add_arguments(sub, command.func)

        return parser

    def _add_arguments(self, parser: ArgumentParser, func: Callable) -> None:
        sig = signature(func)
        for param in sig.parameters.values():
            kwargs = {}
            if param.default is not param.empty:
                kwargs["default"] = param.default
            # In Python 3.14+, argparse validates help strings and rejects
            # non-string types. Convert annotations to string representation.
            help_text = None
            if param.annotation is not param.empty:
                if isinstance(param.annotation, str):
                    help_text = param.annotation
                else:
                    help_text = getattr(
                        param.annotation, "__name__", str(param.annotation)
                    )
            parser.add_argument(
                f"--{param.name}",
                help=help_text,
                **kwargs,
            )
