import argparse
import shlex
from functools import partial
from pathlib import Path

from trim import execute

from .base import AppActions, AppArgument, AppFunction, ConfigMixin
from .base import Help as BHelp
from .base import SubHelpFormatter, print_help

# def print_help(parser):

#     # retrieve subparsers from parser
#     subparsers_actions = [
#         action for action in parser._actions
#         if isinstance(action, argparse._SubParsersAction)]
#     # there will probably only be one subparser_action,
#     # but better safe than sorry
#     for subparsers_action in subparsers_actions:
#         # get all subparsers and print help

#         for choice, subparser in subparsers_action.choices.items():
#             print("--- Subparser '{}'".format(choice))
#             subparser.formatter_class = SubHelpFormatter
#             print(subparser.format_help())


class Help(BHelp):
    double_dash = (
        "apply a double-switch before the " "input to ensure switch aggregation."
    )
    pattern_append = "Append a partial string to the filename split pattern"


class GraphApps(object):
    verbose = 0

    def build_graph_parsers(self, top, parser=None, depth=0, position=None):
        """Given a nested tree of parsers build sub apps into the argparser

        {
            "graph": {
                "keys": [
                    "download",
                ],
                "download": {
                    "depth": 0,
                    "keys": ["db"],
                    "position": ["db"],
                    "items": [
                        "download"
                    ],
                    "db": {
                        "depth": 1,
                        "keys": [],
                        "position": [],
                        "items": [
                            "db"
                        ]
                    }
                }
            },
            "db": "py scripts/download_remote.py"
        }

        trim download db
        """
        for key in top.get("keys", ()):
            data = top[key]
            inner_keys = data.get("keys", ())
            position = tuple(data.get("position", ()))
            position = (position or ()) + (key,)
            if self.verbose > 0:
                w = " " * depth
                print(w, "Adding", position)
            # build a caller
            func = partial(self.depthed_default_caller, data)
            # Push into the parsers
            sub_parser = self.add_sub(
                func, key, parser, formatter_class=SubHelpFormatter
            )
            # recurse.
            self.build_graph_parsers(
                data, sub_parser, depth=depth + 1, position=position
            )

    def depthed_default_caller(self, content, args=None):
        items = content.get("items", ())
        conf = self.get_conf()
        datas = {x: conf.get(x) for x in items}
        self.namespace = args
        self.run_units(datas)
        return "res from default_caller"

    def run_units(self, datas):
        # print('\nKey', datas)
        # print('  default_caller: Key', self.namespace)
        for i, (k, d) in enumerate(datas.items()):
            self.execute_step(i, k, d)

    def execute_step(self, i, k, d):
        pass


class StepExecute(object):

    def execute_step(self, i, k, d):
        # Shell true, because the user is building the scripts.
        command = shlex.split(d)
        command += self.namespace.unknown_args
        print("  Running", k, " -> ", command)
        execute.read_one_stream_command(command)


class TrimApp(AppActions, StepExecute, GraphApps):
    """TrimApp extends AppActions to present provide trim app functions
    for the CLI

    """

    prog_name = "trim"
    primary_name = "trim"

    def setup(self):
        conf = self.get_conf()
        graph = conf.get("graph", {})
        self.verbose = conf.get("verbose", 0)
        self.build_graph_parsers(graph)

    def scripts_func(self, args):
        print("scripts_func", args)
        return "res from scripts_func"

    def default_caller(self, args):
        print_help(self.get_primary_parser())
        return "res from default_caller"

    def add(self, *app_function):
        self.app_functions += app_function


class TrimAdminApp(TrimApp):
    register_name = "admin"

    def get_conf(self):
        return {}


class VerboseSwitch(AppArgument):

    def setup_args(self, parser):
        parser.add_argument("--verbose", "-v", action="count", default=0)


class VersionSwitch(AppArgument):
    _version = "1.0"
    target = "primary"

    def setup_args(self, parser):
        parser.add_argument(
            "--version", action="version", version=f"%(prog)s {self._version}"
        )


class Scripts(AppFunction):
    # name = 'scripts'
    help = Help.scripts
    register_name = "admin"


class ScriptsAddFilenameArg(AppArgument):
    """The 'filename' argument for the `scripts add` function.

    This is applied through the `arguments` of ScriptsAdd.

    As this defines early, it will fail the primary hook parser.
    """

    target = "scripts.add"

    def setup_args(self, parser):
        parser.add_argument(
            "filename", type=str, nargs="*", help=Help.add_filename, default="."
        )
        parser.add_argument(
            "--", dest="_", nargs="+", type=str, default=None, help=Help.double_dash
        )
        parser.add_argument(
            "--type",
            type=str,
            default="raw",
            choices=(
                "raw",
                "path",
            ),
        )
        parser.add_argument(
            "-n",
            "--name",
            type=str,
            default=None,
        )
        parser.add_argument(
            "-d",
            "--dry",
            action="store_true",
        )
        parser.add_argument(
            "-f",
            "--dry-full",
            action="store_true",
        )
        parser.add_argument("-p", "--pattern", type=str, default=None)
        parser.add_argument(
            "-pa", "--pattern-append", type=str, default=None, help=Help.pattern_append
        )


import re
from pprint import pprint as pp


class DefaultHelp(AppArgument):
    target = "primary"

    def setup_args(self, parser):
        # parser.add_help = False
        del parser._option_string_actions["--help"]
        del parser._option_string_actions["-h"]
        parser.add_argument("-h", "--help", action="help")
        # parser.set_defaults(func=self.caller)

    def caller(self, *args, **kwargs):
        print("HELP", args, kwargs)

    # def prep(self, parser):
    #     print('DefaultHelp.prep')
    #     parser.primary_init_kwargs['add_help'] = False
    #     return super().prep(parser)


class ScriptInstall(ConfigMixin):
    """Install a script given through the CLI by file."""

    def parse_script_path_place(self, parsed, **kwargs):
        return parsed.filename.absolute().as_posix()

    def parse_raw_path_place(self, parsed, **kwargs):
        return str(parsed.filename)

    def as_path(self, parts):
        return Path("".join(parts))

    def install(self, parsed, **kwargs):
        path = getattr(self, f"parse_{parsed.type}_path_place")(parsed, **kwargs)

        name = parsed.name or self.as_path(parsed.filename).stem
        parts = self.parse_name(name, parsed.pattern_append, parsed.pattern)
        print("Installing", name)
        data = self.get_conf()
        if parsed.dry:
            r = self.append_graph(data if parsed.dry_full else {}, parts, name)
            r[name] = path
            pp(r)
            return
        self.append_graph(data, parts, name)
        data[name] = path
        self.write_conf_data(data)

    def append_graph(self, data, parts, name, graph=None):

        graph = graph or data.get("graph", None)
        if "graph" not in data:
            data["graph"] = {"keys": []}
        leaf = data["graph"]

        for i, part in enumerate(parts):
            if part not in leaf:
                print("Add leaf", part)
                leaf[part] = {"depth": i, "keys": [], "position": parts[0:i]}
            keys = set(leaf["keys"])
            keys.add(part)
            leaf["keys"] = tuple(keys)
            leaf = leaf[part]

        items = set(leaf.get("items") or [])
        items.add(name)
        leaf["items"] = tuple(items)
        return data

    def parse_name(self, name, append_pattern="", pattern=None):
        safe_ap = append_pattern or ""
        pattern = pattern or f"[_ -{safe_ap}]"
        return re.split(pattern, name)


class ScriptsAdd(AppFunction):
    """The "scripts add [filename]" command for installing a command into
    the persistent store.
    """

    parent_name = "scripts"
    name = "add"
    help = Help.add
    register_name = "admin"

    arguments = (ScriptsAddFilenameArg,)

    def hook(self, parsed, *args, **kwargs):
        print("hook", parsed)
        si = ScriptInstall()
        si.install(parsed, **kwargs)
        return "res from hook"


actions = TrimApp()
# actions.add(VerboseSwitch, VersionSwitch, Scripts, ScriptsAdd, ScriptsAddFilenameArg)
actions.prep()

admin_actions = TrimAdminApp()
admin_actions.add(AppActions, Scripts)
# admin_actions.add(VerboseSwitch, VersionSwitch, Scripts, ScriptsAdd, ScriptsAddFilenameArg)
admin_actions.prep()


def main():
    print("Entry from main")
    actions.run_hook()


def main_admin():
    print("Admin entry from main")
    admin_actions.run_hook()
