"""
A plux CLI frontend. Currently it heavily relies on setuptools, but should be extended in the future to support other
build backends like hatchling or poetry.
"""

import argparse
import json
import logging
import os
import sys

from plux.build import config
from plux.build.setuptools import (
    create_plugin_index_builder,
    find_egg_info_dir,
    get_distribution_from_workdir,
    get_plux_json_path,
)


def entrypoints(args: argparse.Namespace):
    dist = get_distribution_from_workdir(os.getcwd())
    cfg = config.read_plux_config_from_workdir(os.getcwd())
    cfg.merge(
        exclude=args.exclude.split(",") if args.exclude else None,
        include=args.include.split(",") if args.include else None,
    )

    print(f"entry point build mode: {cfg.entrypoint_build_mode.value}")

    if cfg.entrypoint_build_mode == config.EntrypointBuildMode.BUILD_HOOK:
        # TODO: this is code specific to setuptools. integrating additional build tools would require refactoring.
        print("discovering plugins ...")
        dist.command_options["plugins"] = {
            "exclude": ("command line", args.exclude),
            "include": ("command line", args.include),
        }
        dist.run_command("plugins")

        print(f"building {dist.get_name().replace('-', '_')}.egg-info...")
        dist.run_command("egg_info")

        print("discovered plugins:")
        # print discovered plux plugins
        with open(get_plux_json_path(dist)) as fd:
            plux_json = json.load(fd)
            json.dump(plux_json, sys.stdout, indent=2)

    elif cfg.entrypoint_build_mode == config.EntrypointBuildMode.MANUAL:
        path = os.path.join(os.getcwd(), cfg.entrypoint_static_file)

        print(f"discovering plugins and writing to {path} ...")
        builder = create_plugin_index_builder(cfg, dist, output_format="ini")
        with open(path, "w") as fd:
            builder.write(fd)


def discover(args: argparse.Namespace):
    dist = get_distribution_from_workdir(os.getcwd())
    cfg = config.read_plux_config_from_workdir(os.getcwd())
    cfg = cfg.merge(
        exclude=args.exclude.split(",") if args.exclude else None,
        include=args.include.split(",") if args.include else None,
        path=args.path,
    )

    builder = create_plugin_index_builder(cfg, dist, output_format=args.format)
    builder.write(args.output)


def show(args: argparse.Namespace):
    egg_info_dir = find_egg_info_dir()
    if not egg_info_dir:
        print("no *.egg-info directory")
        return

    txt = os.path.join(egg_info_dir, "entry_points.txt")
    if not os.path.isfile(txt):
        print("no entry points to show")
        return

    with open(txt) as fd:
        print(fd.read())


def resolve(args):
    for p in sys.path:
        print(f"path = {p}")
    from plux import PluginManager

    manager = PluginManager(namespace=args.namespace)

    for spec in manager.list_plugin_specs():
        print(f"{spec.namespace}:{spec.name} = {spec.factory.__module__}:{spec.factory.__name__}")


def main(argv=None):
    parser = argparse.ArgumentParser(description="Plux CLI frontend")
    parser.add_argument(
        "--workdir",
        type=str,
        default=os.getcwd(),
        help="overwrite the working directory",
    )
    parser.add_argument("-v", "--verbose", action="store_true", help="enable verbose logging")
    subparsers = parser.add_subparsers(title="commands", dest="command", help="Available commands")

    # Subparser for the 'generate' subcommand
    generate_parser = subparsers.add_parser("entrypoints", help="Discover plugins and generate entry points")
    generate_parser.add_argument(
        "-e",
        "--exclude",
        help="a sequence of paths to exclude; '*' can be used as a wildcard in the names. 'foo.*' will exclude all subpackages of 'foo' (but not 'foo' itself).",
    )
    generate_parser.add_argument(
        "-i",
        "--include",
        help="a sequence of paths to include; If it's specified, only the named items will be included. If it's not "
        "specified, all found items in the path will be included. 'include' can contain shell style wildcard "
        "patterns just like 'exclude'",
    )
    generate_parser.set_defaults(func=entrypoints)

    # Subparser for the 'discover' subcommand
    discover_parser = subparsers.add_parser("discover", help="Discover plugins and print them")
    discover_parser.add_argument(
        "-p",
        "--path",
        help="the file path where to look for plugins'",
    )
    discover_parser.add_argument(
        "-e",
        "--exclude",
        help="a sequence of paths to exclude; '*' can be used as a wildcard in the names. 'foo.*' will exclude all subpackages of 'foo' (but not 'foo' itself).",
    )
    discover_parser.add_argument(
        "-i",
        "--include",
        help="a sequence of paths to include; If it's specified, only the named items will be included. If it's not "
        "specified, all found items in the path will be included. 'include' can contain shell style wildcard "
        "patterns just like 'exclude'",
    )
    discover_parser.add_argument(
        "-f",
        "--format",
        help="the format in which to output the entrypoints. can be 'json' or 'ini', defaults to 'dict'.",
        default="json",
        choices=["json", "ini"],
    )
    discover_parser.add_argument(
        "-o",
        "--output",
        type=argparse.FileType("w"),
        default=sys.stdout,
        help="Output file path, defaults to stdout",
    )
    discover_parser.set_defaults(func=discover)

    # Subparser for the 'resolve' subcommand
    resolve_parser = subparsers.add_parser(
        "resolve", help="Resolve a plugin namespace and list all its plugins"
    )
    resolve_parser.add_argument("--namespace", help="the plugin namespace", required=True)
    resolve_parser.set_defaults(func=resolve)

    # Subparser for the 'discover' subcommand
    show_parser = subparsers.add_parser("show", help="Show entrypoints that were generated")
    show_parser.set_defaults(func=show)

    args = parser.parse_args(argv)

    if args.verbose:
        logging.basicConfig(level=logging.DEBUG)

    os.chdir(args.workdir)

    if not hasattr(args, "func"):
        parser.print_help()
        exit(1)
    args.func(args)


if __name__ == "__main__":
    main()
