#!/usr/bin/env sage-bootstrap-python
"""
Python rewrite of `build/bin/sage-spkg-info` with suggestions applied:
  - Rewrote actually most of the original script because 
  - Utilizes Python's logging (instead of print) for debug/info.
    - Re-use/initialize logging via sage_bootstrap if available.
  - Call `sage-print-system-package-command` instead of writing sudo lines.

NOTE: Many functions presume Sage's bootstrap helpers are in PATH (e.g. `sage-package`).
      Could try to Adjust `SAGE_ROOT`, PATH, or run from within bootstrap like within the original script.

Could try the following invoking method:
cd ~/passagemath (SAGE_ROOT)
export SAGE_ROOT="$PWD"
export PATH="$PWD/build/bin:$PATH"
python3 build/bin/sage-spkg-info.py 4ti2 --output-rst --log-level INFO
"""

from __future__ import print_function, unicode_literals
import sys
import os
import io
import glob

# Make .../build importable so `import sage_bootstrap` works
BUILD_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.insert(0, BUILD_DIR)

# .../passagemath
_SAGE_ROOT_DEFAULT = os.path.dirname(BUILD_DIR)
os.environ.setdefault("SAGE_ROOT", _SAGE_ROOT_DEFAULT)

# To Prepend .../build/bin to PATH
os.environ["PATH"] = os.path.join(BUILD_DIR, "bin") + ":" + os.environ.get("PATH", "")

os.environ["ENABLE_SYSTEM_SITE_PACKAGES"] = "yes"

SYSTEM_NAME_MAP = {
    "alpine": "Alpine",
    "arch": "Arch Linux",
    "conda": "conda-forge",
    "debian": "Debian/Ubuntu",
    "fedora": "Fedora/Redhat/CentOS",
    "freebsd": "FreeBSD",
    "gentoo": "Gentoo Linux",
    "homebrew": "Homebrew",
    "macports": "MacPorts",
    "mingw": "mingw-w64",
    "nix": "Nixpkgs",
    "openbsd": "OpenBSD",
    "opensuse": "openSUSE",
    "pip": "PyPI",
    "slackware": "Slackware",
    "spkg": "Sage distribution",
    "void": "Void Linux",
}
# This is to map system names to display names 

import argparse
import logging
import re
import subprocess
from sage_bootstrap.package import Package

def _package_from_dir(pkg_base, pkg_dir):
    """
    Build a Package given package base name and its directory, trying
    multiple APIs for bootstrap compatibility.
    """
    # Try to use classmethod that clearly takes a filesystem path
    if hasattr(Package, "from_dir"):
        return Package.from_dir(pkg_dir)
    if hasattr(Package, "from_path"):
        return Package.from_path(pkg_dir)

    # Adjustment For Some versions accept a keyword 'directory='
    try:
        return Package(directory=pkg_dir)
    except TypeError:
        pass

    # Last resort: lowercased name-based constructor
    return Package(pkg_base)

def resolve_package(pkg_base):
    """
    To Return (pkg, pkg_dir: Path, pkg_scripts: Path, pkg_type: Optional[str])
    instead of using 'sage-package properties'.
    """
    sage_root = os.path.realpath(os.environ.get("SAGE_ROOT", ""))
    if not sage_root:
        logger.error("SAGE_ROOT is not set; cannot locate packages.")
        raise SystemExit(2)

    pkg_dir = os.path.join(sage_root, "build", "pkgs", pkg_base)
    if not os.path.isdir(pkg_dir):
        logger.error("Package directory not found: %s", pkg_dir)
        raise SystemExit(1)

    # To Construct the Package object directly
    try:
        pkg = _package_from_dir(pkg_base, pkg_dir)
    except Exception as e:
        logger.error("Failed to construct Package from %s: %s", pkg_dir, e)
        raise SystemExit(1)

    # Scripts & metadata files (SPKG.rst, distros/, etc.) live in pkg_dir
    pkg_scripts = pkg_dir

    # Get package type (base/standard/optional/experimental)
    pkg_type = None
    # To Handle both Enum and plain-string representations with grace,
    if hasattr(pkg, "package_type"):
        t = getattr(pkg, "package_type")
        pkg_type = getattr(t, "value", None) or str(t)
    elif hasattr(pkg, "type"):
        t = getattr(pkg, "type")
        pkg_type = getattr(t, "value", None) or str(t)
    if isinstance(pkg_type, str):
        # To Normalize (if any) common forms like 'PackageType.OPTIONAL' -> 'optional'
        pkg_type = pkg_type.split(".")[-1].lower()

    return pkg, pkg_dir, pkg_scripts, pkg_type


def _strip_comments_and_collapse(path):  # path: str
    with io.open(path, "r", encoding="utf-8") as fh:
        txt = fh.read()
    """Mimic `sed 's/#.*//'` + whitespace collapsing from the bash script."""
    txt = re.sub(r"#.*", "", txt)
    return " ".join(txt.split()).strip()

# Logging
try:  # pragma: no cover - to choose optional path
    import sage_bootstrap.logging as _sb_logging  # noqa: F401
except Exception:  # pragma: no cover - to ignore if not available
    pass

logger = logging.getLogger("sage-spkg-info")
if not logger.handlers:  # To go back to basic config if none set by sage_bootstrap
    logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

# CLI: To parse arguments (Rewrite version)
def parse_args():
    parser = argparse.ArgumentParser(
        description="Fetch and format Sage package information (Python rewrite)."
    )
    parser.add_argument(
        "packages",
        nargs="+",
        help="One or more package base names (separate by space) (e.g. 4ti2 pari ntl)",
    )
    parser.add_argument(
        "--output-dir", dest="output_dir", default=None,
        help="Directory to write the formatted output (default: stdout)",
    )
    parser.add_argument(
        "--output-rst", dest="output_rst", action="store_true",
        help="Emit reStructuredText markup instead of plain text",
    )
    parser.add_argument(
        "--log-level", dest="log_level", default="WARNING",
        choices=["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
        help="Set logging verbosity (default: WARNING)",
    )
    return parser.parse_args()


# To run the Helpers

from sage_bootstrap.compat import quote

if sys.version_info < (3, 7, 0, 0, 0):

    from subprocess import check_output

    def run(cmd, check=True, text=True):
        logger.debug("Running command: %s", " ".join(map(quote, cmd)))
        return check_output(cmd).decode('utf8')
else:

    from subprocess import run as subprocess_run

    def run(cmd, check=True, text=True):
        # Run the subprocess and log on DEBUG.
        logger.debug("Running command: %s", " ".join(map(quote, cmd)))
        return subprocess_run(cmd, capture_output=True, text=text, check=check).stdout


# For the purpose of better looking, Output formatting helpers 
# Splitted the helper into two small classes, RstFormatter and PlainFormatter
class _BaseFormatter:
    """Common API For fixing output behavior."""
    rst = False

    # To default for plain text
    def ref(self, x):               # type: (str) -> str
            return x

    def spkg(self, x):              # type: (str) -> str
        return x

    def issue(self, x):             # type: (str) -> str
        return "https://github.com/sagemath/sage/issues/{}".format(x)

    def code(self, *args):          # type: (*str) -> str
        return " ".join(args)

    def tab(self, x):               # type: (str) -> str
        return "{}:".format(x)

    def anchor(self, name):         # type: (str) -> str
        return ""                   # optional helper

class PlainFormatter(_BaseFormatter):
    rst = False
    # Did not change default behavior.

class RstFormatter(_BaseFormatter):
    rst = True
    def ref(self, x):
        return ":ref:`{}`".format(x)

    def spkg(self, x):
        return self.ref("spkg_{}".format(x))

    def issue(self, x):
        return ":issue:`{}`".format(x)

    def code(self, *args):
        return "``{}``".format(" ".join(args))

    def tab(self, x):
        return ".. tab:: {}".format(x)

    def anchor(self, name):
        return ".. _{}:\n".format(name)

def make_formatter(output_rst):
    return RstFormatter() if output_rst else PlainFormatter()

# Core functionality
# To Read and post-process SPKG.rst (README).
def process_spkg_file(pkg_base, pkg_scripts, fmt, out):
    spkg_file = os.path.join(pkg_scripts, "SPKG.rst")
    if not os.path.isfile(spkg_file):
        logger.info("No SPKG.rst for %s", pkg_base)
        return

    with io.open(spkg_file, "r", encoding="utf-8") as fh:
        content = fh.read()

    # Modify SPKG.rst that are symlinks to a README.rst
    # - change title
    # - remove boilerplate
    if "About this pip-installable distribution package" in content:
        content = re.sub('^ *passagemath:' ,
                         pkg_base + ':', content, flags=re.MULTILINE)
        content = re.sub('^====',
                         '============================', content, flags=re.MULTILINE)
        content = re.sub('`passagemath.*\nAbout this pip-installable distribution package\n-+\n',
                         '', content, flags=re.DOTALL|re.MULTILINE)

    # Tweaks to make identical to the bash script 
    content = re.sub(r'https://github\.com/sagemath/sage/issues/(\d+)',
                     r':issue:`\1`', content)
    content = re.sub(r'https://arxiv\.org/abs/cs/(\d+)',
                     r':arxiv:`cs/\1`', content)
    print(content, file=out)


def display_dependencies(pkg_base, fmt, out):
    try:
        result = run(["sage-package", "dependencies",
                      "--format={}".format("rst" if fmt.rst else "plain"),
                      pkg_base],
                     check=True)
    except subprocess.CalledProcessError as e:
        logger.warning("Could not get dependencies for %s: %s", pkg_base, e.stderr.strip())
        return

    print("Dependencies", file=out)
    print("------------", file=out)
    print("", file=out)
    print(result.strip(), file=out)
    

def display_version_info(pkg_base, fmt, out):
    try:
        result = run(["sage-get-system-packages", "versions", pkg_base], check=True)
    except subprocess.CalledProcessError as e:
        logger.warning("Could not get version info for %s: %s", pkg_base, e.stderr.strip())
        result = ''

    result = result.strip()

    try:
        repology = run([
            "sage-print-system-package-command", "repology",
            "--format={}".format("rst" if fmt.rst else "plain"),
            "--wrap", "--prompt=    $ ", "--continuation=          ",
            "--sudo", "--spkg", "install",
            pkg_base
        ])
    except subprocess.CalledProcessError as e:
        logger.warning("repology cmd failed: %s", e.stderr.strip())
        repology = ''

    repology = repology.strip()

    if result or repology:
        print("Version Information", file=out)
        print("-------------------", file=out)
        if result:
            print("", file=out)
            print(result, file=out)
        if repology:
            print("", file=out)
            print(repology, file=out)


# To Build the "Equivalent System Packages" section and Use sage-print-system-package-command.
def handle_system_packages(pkg_base, pkg_scripts, fmt, out):

    pkg = _package_from_dir(pkg_base, pkg_scripts)  # or to change to Package.from_base(pkg_base), depending on types of API

    print("Installation commands", file=out)
    print("---------------------", file=out)
    print("", file=out)

    distros_dir = os.path.join(pkg_scripts, "distros")

    systems = []
    if pkg.distribution_name:
        systems.append("pip")

    systems.append("spkg")

    for entry in sorted(glob.glob(os.path.join(distros_dir, "*.txt"))):
        name = os.path.splitext(os.path.basename(entry))[0]
        if name != "repology":
            systems.append(name)

    for system in systems:
        heading = "{}:".format(SYSTEM_NAME_MAP.get(system, system))
        print(fmt.tab(heading), file=out)
        indent = "   " if fmt.rst else ""

        if system == 'spkg' and pkg.source == 'none':
            print("", file=out)
            print("{}This is a dummy package and cannot be installed "
                  "using the Sage distribution.".format(indent), file=out)
            print("", file=out)
            continue

        args = [
            "sage-print-system-package-command",
            system,
            "--format={}".format("rst" if fmt.rst else "plain"),
            "--wrap",
            "--prompt={}".format("    $ "),
            "--continuation={}".format("          "),
            "--indent={}".format(indent),
            "--sudo",
            "--spkg",
            "install",
            pkg_base
        ]
        try:
            cp = run(args)
            print(cp.rstrip(), file=out)
        except subprocess.CalledProcessError as e:
            logger.warning("Failed to get system package command for %s on %s: %s",
                           pkg_base, system, e.stderr.strip())
        print("", file=out)

    # To print message about spkg-configure.m4. Optionally: expose as @property later.
    print("", file=out)
    if pkg.has_spkg_configure:
        if pkg.uses_python_package_check:
            print("If the system package is installed and if the (experimental) option", file=out)
            print("{} is passed to {}, then \n{} will check if the system package can be used."
                  .format(fmt.code('--enable-system-site-packages'),
                          fmt.code('./configure'),
                          fmt.code('./configure')),
                  file=out)
        else:
            cfg_word = "./configure"
            if fmt.rst:
                cfg_word = "``{}``".format(cfg_word)
            print("If the system package is installed, {} will check if it can be used.".format(cfg_word), file=out)
    else:
        if not pkg_base.startswith("_"):
            print("However, these system packages will not be used for building Sage", file=out)
            print("because {} has not been written for this package;".format(fmt.code('spkg-configure.m4')), file=out)
            print("see {} for more information.".format(fmt.issue('27330')), file=out)


# To Emit info for multiple packages.
def emit_for_package(pkg_base, fmt, out_dir):
    pkg, pkg_dir, pkg_scripts, pkg_type = resolve_package(pkg_base)

    # Local helper to print all sections
    def _emit_sections(out_stream):
        if fmt.rst and out_dir:
            print(".. _spkg_{}:\n".format(pkg_base), file=out_stream)

        # SPKG.rst content
        process_spkg_file(pkg_base, pkg_scripts, fmt, out_stream)

        # Package type
        if pkg_type:
            print("\nType\n----\n", file=out_stream)
            print(pkg_type, file=out_stream)
            print("", file=out_stream)  # blank line after the value

        # Dependencies (formatting handled elsewhere)
        print("", file=out_stream)
        display_dependencies(pkg_base, fmt, out_stream)

        # Version info
        print("", file=out_stream)
        display_version_info(pkg_base, fmt, out_stream)

        # System packages
        print("", file=out_stream)
        handle_system_packages(pkg_base, pkg_scripts, fmt, out_stream)

    if out_dir:
        if not os.path.isdir(out_dir):
            os.makedirs(out_dir)
        fname = "{}.rst".format(pkg_base) if fmt.rst else "{}.txt".format(pkg_base)
        target = os.path.join(out_dir, fname)
        logger.info("Writing output to %s", target)
        with io.open(target, "w", encoding="utf-8") as out:
            _emit_sections(out)
    else:
        _emit_sections(sys.stdout)


# Main

def main():
    args = parse_args()
    logging.getLogger().setLevel(getattr(logging, args.log_level))
    logger.debug("Arguments: %s", args)

    fmt = make_formatter(args.output_rst)
    out_dir = args.output_dir if args.output_dir else None

    for i, pkg_base in enumerate(args.packages):
        # Nice visual separator when writing to stdout
        if not out_dir and i > 0:
            print("\n" + "-" * 79 + "\n")
        emit_for_package(pkg_base, fmt, out_dir)

if __name__ == "__main__":
    main()
