#!/usr/bin/env python3
"""Package a subset of a monorepo and determine the dependent packages."""

import argparse
import pathlib
import sys
from typing import List

import packagery
import pypackagery_meta


def main() -> int:
    """Execute the main routine."""
    # pylint: disable=too-many-locals
    parser = argparse.ArgumentParser(description=pypackagery_meta.__description__)
    parser.add_argument("--root_dir", help="Root directory of the Python files in the monorepo", required=True)
    parser.add_argument(
        "--initial_set",
        help="Paths to the files for which we want to compute the dependencies.\n\n"
        "If you specify a directory, all *.py files beneath (including subdirectories) are considered as part of "
        "the initial set.",
        nargs='+',
        required=True)

    parser.add_argument(
        "--format",
        help="The format of the output depedendency graph; default is the verbose, human-readable format",
        choices=packagery.FORMATS,
        default='verbose')

    parser.add_argument(
        "--dont_panic",
        help="If set, does not return an error code if some of the dependencies could not be resolved",
        action="store_true")

    parser.add_argument("--output_path", help="If set, outputs the result to a file instead of STDOUT")

    args = parser.parse_args()

    root_dir = pathlib.Path(args.root_dir).absolute()
    fmt = str(args.format)
    dont_panic = bool(args.dont_panic)
    output_path = None if not args.output_path else pathlib.Path(args.output_path)

    assert isinstance(args.initial_set, list)
    assert all(isinstance(item, str) for item in args.initial_set)

    initial_set = args.initial_set  # type: List[str]

    initial_pths = [pathlib.Path(pth).absolute() for pth in initial_set]

    for pth in initial_pths:
        if root_dir != pth and root_dir not in pth.parents:
            raise ValueError(("Expected all the files of the initial set to reside beneath root directory {}, "
                              "but at least one does not: {}").format(root_dir, pth))

    initial_files = packagery.resolve_initial_paths(initial_paths=initial_pths)

    # yapf: disable
    rel_pths = [pth.relative_to(root_dir) for pth in initial_files]
    # yapf: enable

    requirements_txt = root_dir / "requirements.txt"
    if not requirements_txt.exists():
        raise FileNotFoundError(("requirements.txt expected beneath the root directory {}, "
                                 "but could not be found: {})".format(root_dir, requirements_txt)))

    module_to_requirement_tsv = root_dir / "module_to_requirement.tsv"
    if not module_to_requirement_tsv.exists():
        raise FileNotFoundError(("module_to_requirement.tsv expected beneath the root directory {}, "
                                 "but could not be found: {})".format(root_dir, module_to_requirement_tsv)))

    requirements = packagery.parse_requirements(text=requirements_txt.read_text(), filename=str(requirements_txt))

    module_to_requirement = packagery.parse_module_to_requirement(
        text=module_to_requirement_tsv.read_text(), filename=str(module_to_requirement_tsv))

    missing_reqs = packagery.missing_requirements(
        module_to_requirement=module_to_requirement, requirements=requirements)

    if len(missing_reqs) > 0:
        raise RuntimeError(
            ("The requirements listed in moudle_to_requirement mapping are missing in requirements file:\n"
             "module-to-requirement file: {}\n"
             "requirements file: {}\n"
             "Missing requirements:\n{}").format(module_to_requirement_tsv, requirements_txt, "\n".join(missing_reqs)))

    pkg = packagery.collect_dependency_graph(
        root_dir=root_dir, rel_paths=rel_pths, requirements=requirements, module_to_requirement=module_to_requirement)

    if output_path is None:
        packagery.output(package=pkg, a_format=fmt)
    else:
        with output_path.open('w') as fid:
            packagery.output(package=pkg, out=fid, a_format=fmt)

    if not dont_panic and len(pkg.unresolved_modules) > 0:
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())
