import argparse
import sys
from typing import Optional

from cargo2rpm.rpm import InvalidFeatureError


def get_args(args=None):
    """
    Constructs the command-line argument parser for cargo2rpm.

    If arguments are passed to this function, they are parsed instead of parsing
    actual command line arguments. This is useful for testing parser behaviour.
    """

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-p",
        "--path",
        help="Path to Cargo.toml (for current crate or workspace root)",
    )
    parser.set_defaults(action=None)
    action_parsers = parser.add_subparsers()

    action_brs = action_parsers.add_parser(
        "buildrequires",
        help="Print BuildRequires for the current crate",
    )
    action_brs.add_argument(
        "-t",
        "--with-check",
        action="store_true",
        help="Include dev-dependencies",
    )
    action_brs.add_argument(
        "-a",
        "--all-features",
        action="store_true",
        help="Enable all features",
    )
    action_brs.add_argument(
        "-n",
        "--no-default-features",
        action="store_true",
        help="Disable default features",
    )
    action_brs.add_argument(
        "-f",
        "-F",
        "--features",
        help="Comma-separated list of features to enable",
    )
    action_brs.set_defaults(action="buildrequires")

    action_req = action_parsers.add_parser(
        "requires",
        help="Print Requires for the current crate and the given feature.",
    )
    action_req.add_argument(
        "-f",
        "-F",
        "--feature",
        help="Name of the feature to generate Requires for.",
    )
    action_req.add_argument(
        "-s",
        "--subpackage",
        action="store_true",
        help="Treat the argument as a subpackage name.",
    )
    action_req.set_defaults(action="requires")

    action_prov = action_parsers.add_parser(
        "provides",
        help="Print Provides for the current crate and the given feature.",
    )
    action_prov.add_argument(
        "-f",
        "-F",
        "--feature",
        help="Name of the feature to generate Provides for.",
    )
    action_prov.add_argument(
        "-s",
        "--subpackage",
        action="store_true",
        help="Treat the argument as a subpackage name.",
    )
    action_prov.set_defaults(action="provides")

    action_name = action_parsers.add_parser(
        "name",
        help="Print the name of the current crate.",
    )
    action_name.set_defaults(action="name")

    action_version = action_parsers.add_parser(
        "version",
        help="Print the version of the current crate.",
    )
    action_version.set_defaults(action="version")

    action_is_lib = action_parsers.add_parser(
        "is-lib",
        help="Print 1 if the current crate is a library target, otherwise print 0",
    )
    action_is_lib.set_defaults(action="is-lib")

    action_is_bin = action_parsers.add_parser(
        "is-bin",
        help="Print 1 if the current crate has binary targets, otherwise print 0",
    )
    action_is_bin.set_defaults(action="is-bin")

    action_semver_to_rpm = action_parsers.add_parser(
        "semver2rpm",
        help="Convert SemVer version string to equivalent RPM format",
    )
    action_semver_to_rpm.add_argument(
        "version",
        help="SemVer compliant version string",
    )
    action_semver_to_rpm.set_defaults(action="semver2rpm")

    action_rpm_to_semver = action_parsers.add_parser(
        "rpm2semver",
        help="Convert RPM version string to equivalent SemVer format",
    )
    action_rpm_to_semver.add_argument(
        "version",
        help="RPM version string",
    )
    action_rpm_to_semver.set_defaults(action="rpm2semver")

    action_parse_vendor_manifest = action_parsers.add_parser(
        "parse-vendor-manifest",
        help="Parse vendor manifest generated by %%cargo_vendor_manifest",
    )
    action_parse_vendor_manifest.set_defaults(action="parse-vendor-manifest")

    action_license_breakdown = action_parsers.add_parser(
        "license-breakdown",
        help="Print license breakdown for statically linked dependencies",
    )
    action_license_breakdown.add_argument(
        "-p",
        "--package",
        help="Limit to one specific workspace member",
        nargs="?",
    )
    action_license_breakdown.add_argument(
        "-a",
        "--all-features",
        action="store_true",
        help="Enable all features",
    )
    action_license_breakdown.add_argument(
        "-n",
        "--no-default-features",
        action="store_true",
        help="Disable default features",
    )
    action_license_breakdown.add_argument(
        "-f",
        "-F",
        "--features",
        help="Comma-separated list of features to enable",
    )
    action_license_breakdown.set_defaults(action="license-breakdown")

    action_license_summary = action_parsers.add_parser(
        "license-summary",
        help="Print license summary for statically linked dependencies",
    )
    action_license_summary.add_argument(
        "-p",
        "--package",
        help="Limit to one specific workspace member",
        nargs="?",
    )
    action_license_summary.add_argument(
        "-a",
        "--all-features",
        action="store_true",
        help="Enable all features",
    )
    action_license_summary.add_argument(
        "-n",
        "--no-default-features",
        action="store_true",
        help="Disable default features",
    )
    action_license_summary.add_argument(
        "-f",
        "-F",
        "--features",
        help="Comma-separated list of features to enable",
    )
    action_license_summary.set_defaults(action="license-summary")

    if args is not None:
        return parser.parse_args(args)
    else:  # pragma nocover
        return parser.parse_args()


def get_cargo_toml_paths_from_stdin() -> set[str]:  # pragma nocover
    """
    Read lines from standard input and filter out lines that look like paths
    to `Cargo.toml` files. This is how RPM generators pass lists of files.
    """

    lines = {line.rstrip("\n") for line in sys.stdin.readlines()}
    return {line for line in lines if line.endswith("/Cargo.toml")}


def get_cargo_vendor_txt_paths_from_stdin() -> set[str]:  # pragma nocover
    """
    Read lines from standard input and filter out lines that look like paths
    to `cargo-vendor.txt` files. This is how RPM generators pass lists of files.
    """

    lines = {line.rstrip("\n") for line in sys.stdin.readlines()}
    return {line for line in lines if line.endswith("/cargo-vendor.txt")}


def get_feature_from_subpackage(subpackage: str) -> Optional[str]:
    """
    Parses a Rust crate subpackage name into the name of the corresponding
    crate feature. This is how RPM generators determine which feature to
    generate Provides and Requires for.

    Two formats of arguments are valid:

    - main subpackage (contains source code): `rust-{crate}-devel`
    - feature subpackages (metadata only): `rust-{crate}+{feature}-devel`

    Raises an `InvalidFeatureError` for invalid arguments. This exception
    triggers RPM generators to produce invalid output, which stops RPM builds.
    """

    if not subpackage.startswith("rust-"):
        raise InvalidFeatureError("Invalid subpackage name (missing 'rust-' prefix)")
    if not subpackage.endswith("-devel"):
        raise InvalidFeatureError("Invalid subpackage name (missing '-devel' suffix)")

    crate_plus_feature = subpackage.removeprefix("rust-").removesuffix("-devel")

    if "+" in crate_plus_feature:
        # split only once: feature names can contain "+" characters
        crate, feature = crate_plus_feature.split("+", 1)

        if not crate:
            # invalid format: "rust-+{feature]-devel"
            raise InvalidFeatureError("Invalid subpackage name (crate name cannot be empty or contain '+' characters)")
        return feature

    else:
        return None
