"""low-level ebuild utility"""

import sys

from snakeoil.cli.exceptions import ExitException
from snakeoil.strings import pluralism

from ..operations import OperationError, observer
from ..package.errors import MetadataException
from ..util.commandline import ArgumentParser, StoreTarget

argparser = ArgumentParser(description=__doc__, script=(__file__, __name__))
argparser.add_argument(
    'target', action=StoreTarget,
    allow_ebuild_paths=True, allow_external_repos=True,
    help="atom or ebuild matching a pkg to execute phases from")
argparser.add_argument('phase', nargs='+', help="phases to run")
phase_opts = argparser.add_argument_group("phase options")
phase_opts.add_argument(
    "--no-auto", action='store_true', default=False,
    help="run just the specified phases; "
         "it's up to the invoker to get the order right")


@argparser.bind_final_check
def _validate_args(parser, namespace):
    namespace.repo = namespace.domain.ebuild_repos_unfiltered


@argparser.bind_main_func
def main(options, out, err):
    token, restriction = options.target[0]
    domain = options.domain

    try:
        pkgs = options.repo.match(restriction, pkg_filter=None)
    except MetadataException as e:
        error = e.msg(verbosity=options.verbosity)
        argparser.error(f'{e.pkg.cpvstr}::{e.pkg.repo.repo_id}: {error}')

    if not pkgs:
        argparser.error(f"no matches: {token!r}")

    pkg = max(pkgs)
    if len(pkgs) > 1:
        argparser.err.write(f"got multiple matches for {token!r}:")
        if len(set((p.slot, p.repo) for p in pkgs)) != 1:
            for p in pkgs:
                repo_id = getattr(p.repo, 'repo_id', 'unknown')
                argparser.err.write(f"{p.cpvstr}:{p.slot}::{repo_id}", prefix='  ')
            argparser.err.write()
            argparser.error("please refine your restriction to one match")
        repo_id = getattr(pkg.repo, 'repo_id', 'unknown')
        argparser.err.write(f"choosing {pkg.cpvstr}:{pkg.slot}::{repo_id}", prefix='  ')
        sys.stderr.flush()

    kwds = {}
    phase_obs = observer.phase_observer(observer.formatter_output(out), options.debug)

    phases = [x for x in options.phase if x != 'clean']
    clean = (len(phases) != len(options.phase))

    if options.no_auto:
        kwds["ignore_deps"] = True
        if "setup" in phases:
            phases.insert(0, "fetch")

    # forcibly run test phase if selected
    force_test = 'test' in phases
    if force_test and 'test' in pkg.iuse:
        pkg.use.add('test')

    # by default turn off startup cleans; we clean by ourselves if
    # told to do so via an arg
    build = domain.build_pkg(
        pkg, failed=True, clean=False, observer=phase_obs, force_test=force_test)
    if clean:
        build.cleanup(force=True)
    build._reload_state()

    phase_funcs = [(p, getattr(build, p, None)) for p in phases]
    unknown_phases = [p for p, func in phase_funcs if func is None]
    if unknown_phases:
        argparser.error("unknown phase%s: %s" % (
            pluralism(unknown_phases), ', '.join(map(repr, unknown_phases))))

    try:
        for phase, func in phase_funcs:
            out.write(f'executing phase {phase}')
            func(**kwds)
    except OperationError as e:
        raise ExitException(f"caught exception executing phase {phase}: {e}") from e
