#!/usr/bin/env python3
"""
Development CLI for Synex Package Manager.

This tool is useful before the GUI is ready.
"""

from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
from pathlib import Path


CURRENT_FILE = Path(__file__).resolve()

CANDIDATE_SOURCE_DIRS = [
    CURRENT_FILE.parents[1],                 # development mode: ./src/bin/
    Path("/usr/lib/synex-package-manager"),  # installed mode
]

for source_dir in CANDIDATE_SOURCE_DIRS:
    if (source_dir / "synexpm").exists():
        if str(source_dir) not in sys.path:
            sys.path.insert(0, str(source_dir))
        break


from synexpm.apt_backend import list_upgradable_packages, simulate_upgrade
from synexpm.i18n import _
from synexpm.package_backend import get_package_info, search_packages
from synexpm.repo_backend import scan_repositories
from synexpm.system import command_exists, get_basic_system_report, is_root


def _print_json(data: object) -> None:
    print(json.dumps(data, indent=2, ensure_ascii=False))


def _find_root_helper() -> str:
    """
    Find the root helper.

    Development mode:
        ./src/bin/synexpm-cli
        ./src/bin/synexpm-root-helper

    Installed mode:
        synexpm-root-helper from PATH
    """

    sibling = CURRENT_FILE.with_name("synexpm-root-helper")

    if sibling.exists() and sibling.is_file():
        return str(sibling)

    installed_helper = Path("/usr/lib/synex-package-manager/bin/synexpm-root-helper")

    if installed_helper.exists() and installed_helper.is_file():
        return str(installed_helper)

    return "synexpm-root-helper"


def _run_root_helper(action: str, *, use_pkexec: bool = True) -> int:
    """
    Run a privileged helper action.
    """

    helper = _find_root_helper()

    if is_root() or not use_pkexec:
        command = [helper, action]
    else:
        if not command_exists("pkexec"):
            print(_("pkexec is not available on this system."), file=sys.stderr)
            return 127

        command = ["pkexec", helper, action]

    print(_("Running privileged action: {action}").format(action=action))
    print(_("Command: {command}").format(command=" ".join(command)))
    print()

    process = subprocess.run(command, text=True)

    return process.returncode


def command_system(args: argparse.Namespace) -> int:
    report = get_basic_system_report()

    if args.json:
        _print_json(report)
        return 0

    print(_("System report"))
    print("=" * 60)

    for key, value in report.items():
        print(f"{key}: {value}")

    return 0


def command_repos(args: argparse.Namespace) -> int:
    report = scan_repositories()

    if args.json:
        _print_json(
            {
                "scanned_files": list(report.scanned_files),
                "has_synex_repo": report.has_synex_repo,
                "suites": list(report.suites),
                "warnings": list(report.warnings),
                "entries": [
                    {
                        "entry_id": entry.entry_id,
                        "file_path": entry.file_path,
                        "line_number": entry.line_number,
                        "enabled": entry.enabled,
                        "source_format": entry.source_format,
                        "repo_type": entry.repo_type,
                        "uri": entry.uri,
                        "suite": entry.suite,
                        "components": list(entry.components),
                        "options": entry.options,
                        "signed_by": entry.signed_by,
                        "is_synex": entry.is_synex,
                        "is_debian": entry.is_debian,
                    }
                    for entry in report.entries
                ],
            }
        )
        return 0

    print(_("APT repository report"))
    print("=" * 60)
    print(_("Scanned files: {count}").format(count=len(report.scanned_files)))
    print(_("Repository entries: {count}").format(count=len(report.entries)))
    print(_("Enabled entries: {count}").format(count=len(report.enabled_entries)))
    print(_("Disabled entries: {count}").format(count=len(report.disabled_entries)))
    print(_("Synex repository detected: {value}").format(value=report.has_synex_repo))
    print(_("Suites: {value}").format(value=", ".join(report.suites) or "-"))

    if report.warnings:
        print()
        print(_("Warnings"))
        print("-" * 60)

        for warning in report.warnings:
            print(f"- {warning}")

    print()
    print(_("Enabled repositories"))
    print("-" * 60)

    for entry in report.enabled_entries:
        components = " ".join(entry.components) or "-"
        print(
            f"{entry.file_path}:{entry.line_number} | "
            f"{entry.repo_type} | "
            f"{entry.uri} | "
            f"{entry.suite} | "
            f"{components}"
        )

    if report.disabled_entries:
        print()
        print(_("Disabled repositories"))
        print("-" * 60)

        for entry in report.disabled_entries:
            components = " ".join(entry.components) or "-"
            print(
                f"{entry.file_path}:{entry.line_number} | "
                f"{entry.repo_type} | "
                f"{entry.uri} | "
                f"{entry.suite} | "
                f"{components}"
            )

    return 0


def command_updates(args: argparse.Namespace) -> int:
    updates = list_upgradable_packages()

    if args.json:
        _print_json(
            [
                {
                    "package": update.package,
                    "suite": update.suite,
                    "candidate_version": update.candidate_version,
                    "architecture": update.architecture,
                    "current_version": update.current_version,
                }
                for update in updates
            ]
        )
        return 0

    print(_("Available updates"))
    print("=" * 60)
    print(_("Packages: {count}").format(count=len(updates)))

    if not updates:
        return 0

    print()

    for update in updates:
        current = update.current_version or "-"
        print(
            f"{update.package} | "
            f"{current} -> {update.candidate_version} | "
            f"{update.suite} | "
            f"{update.architecture}"
        )

    return 0


def command_simulate_upgrade(args: argparse.Namespace) -> int:
    result = simulate_upgrade(full=args.full)

    if args.json:
        _print_json(
            {
                "command": list(result.command),
                "return_code": result.return_code,
                "ok": result.ok,
                "packages_to_install": list(result.packages_to_install),
                "packages_to_upgrade": list(result.packages_to_upgrade),
                "packages_to_remove": list(result.packages_to_remove),
                "held_back_packages": list(result.held_back_packages),
                "stdout": result.stdout,
                "stderr": result.stderr,
            }
        )
        return result.return_code

    title = _("Full upgrade simulation") if args.full else _("Safe upgrade simulation")

    print(title)
    print("=" * 60)
    print(_("Command: {command}").format(command=" ".join(result.command)))
    print(_("Return code: {code}").format(code=result.return_code))
    print()

    print(_("Packages to install: {count}").format(count=len(result.packages_to_install)))
    for package in result.packages_to_install:
        print(f"  + {package}")

    print()
    print(_("Packages to upgrade: {count}").format(count=len(result.packages_to_upgrade)))
    for package in result.packages_to_upgrade:
        print(f"  * {package}")

    print()
    print(_("Packages to remove: {count}").format(count=len(result.packages_to_remove)))
    for package in result.packages_to_remove:
        print(f"  - {package}")

    print()
    print(_("Held back packages: {count}").format(count=len(result.held_back_packages)))
    for package in result.held_back_packages:
        print(f"  ! {package}")

    if result.stderr.strip():
        print()
        print(_("stderr"))
        print("-" * 60)
        print(result.stderr.strip())

    return result.return_code


def command_refresh_cache(args: argparse.Namespace) -> int:
    """
    Refresh APT package indexes through the privileged root helper.
    """

    return _run_root_helper(
        "apt-update",
        use_pkexec=not args.no_pkexec,
    )


def command_upgrade_safe(args: argparse.Namespace) -> int:
    """
    Apply safe package upgrades through the privileged root helper.
    """

    return _run_root_helper(
        "apt-upgrade-safe",
        use_pkexec=not args.no_pkexec,
    )



def command_search_packages(args: argparse.Namespace) -> int:
    """
    Search APT packages by name or description.
    """

    from dataclasses import asdict
    import json

    results = search_packages(args.query, limit=args.limit)

    if args.json:
        print(
            json.dumps(
                [asdict(result) for result in results],
                ensure_ascii=False,
                indent=2,
            )
        )
        return 0

    print(_("Package search"))
    print("=" * 60)
    print(_("Query: {query}").format(query=args.query))
    print(_("Results: {count}").format(count=len(results)))
    print()

    if not results:
        print(_("No packages found."))
        return 0

    for result in results:
        installed = _("installed") if result.installed else _("available")
        installed_version = result.installed_version or "-"
        candidate_version = result.candidate_version or "-"

        print(
            "{name} | {status} | installed: {installed_version} | "
            "candidate: {candidate_version} | section: {section}".format(
                name=result.name,
                status=installed,
                installed_version=installed_version,
                candidate_version=candidate_version,
                section=result.section or "-",
            )
        )

        if result.short_description:
            print(f"  {result.short_description}")

    return 0


def command_package_info(args: argparse.Namespace) -> int:
    """
    Show detailed information for one APT package.
    """

    from dataclasses import asdict
    import json

    try:
        details = get_package_info(args.package)
    except ValueError as error:
        print(str(error))
        return 1

    if args.json:
        print(
            json.dumps(
                asdict(details),
                ensure_ascii=False,
                indent=2,
            )
        )
        return 0

    print(_("Package information"))
    print("=" * 60)
    print(_("Package: {package}").format(package=details.name))
    print(_("Installed: {value}").format(value=_("yes") if details.installed else _("no")))

    if details.installed_version:
        print(_("Installed version: {version}").format(version=details.installed_version))

    if details.candidate_version:
        print(_("Candidate version: {version}").format(version=details.candidate_version))

    if details.section:
        print(_("Section: {section}").format(section=details.section))

    if details.priority:
        print(_("Priority: {priority}").format(priority=details.priority))

    if details.architecture:
        print(_("Architecture: {architecture}").format(architecture=details.architecture))

    if details.maintainer:
        print(_("Maintainer: {maintainer}").format(maintainer=details.maintainer))

    if details.installed_size:
        print(_("Installed size: {size} KB").format(size=details.installed_size))

    if details.download_size:
        print(_("Download size: {size} bytes").format(size=details.download_size))

    if details.depends:
        print(_("Depends: {depends}").format(depends=details.depends))

    if details.recommends:
        print(_("Recommends: {recommends}").format(recommends=details.recommends))

    if details.suggests:
        print(_("Suggests: {suggests}").format(suggests=details.suggests))

    if details.homepage:
        print(_("Homepage: {homepage}").format(homepage=details.homepage))

    if details.short_description:
        print()
        print(_("Description: {description}").format(description=details.short_description))

    if details.long_description:
        print(details.long_description)

    return 0



def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="synexpm-cli",
        description=_("Development CLI for Synex Package Manager."),
    )

    subparsers = parser.add_subparsers(dest="command", required=True)

    system_parser = subparsers.add_parser(
        "system",
        help=_("Show basic system information."),
    )
    system_parser.add_argument("--json", action="store_true")
    system_parser.set_defaults(func=command_system)

    repos_parser = subparsers.add_parser(
        "repos",
        help=_("Scan APT repositories."),
    )
    repos_parser.add_argument("--json", action="store_true")
    repos_parser.set_defaults(func=command_repos)

    updates_parser = subparsers.add_parser(
        "updates",
        help=_("List upgradable packages."),
    )
    updates_parser.add_argument("--json", action="store_true")
    updates_parser.set_defaults(func=command_updates)

    simulate_parser = subparsers.add_parser(
        "simulate-upgrade",
        help=_("Simulate a safe or full upgrade."),
    )
    simulate_parser.add_argument("--full", action="store_true")
    simulate_parser.add_argument("--json", action="store_true")
    simulate_parser.set_defaults(func=command_simulate_upgrade)

    refresh_parser = subparsers.add_parser(
        "refresh-cache",
        help=_("Refresh APT package indexes using the root helper."),
    )
    refresh_parser.add_argument(
        "--no-pkexec",
        action="store_true",
        help=_("Do not use pkexec. Useful when already running as root."),
    )
    refresh_parser.set_defaults(func=command_refresh_cache)

    upgrade_safe_parser = subparsers.add_parser(
        "upgrade-safe",
        help=_("Apply safe package upgrades using the root helper."),
    )
    upgrade_safe_parser.add_argument(
        "--no-pkexec",
        action="store_true",
        help=_("Do not use pkexec. Useful when already running as root."),
    )
    upgrade_safe_parser.set_defaults(func=command_upgrade_safe)

    search_parser = subparsers.add_parser(
        "search",
        help=_("Search APT packages by name or description."),
    )
    search_parser.add_argument(
        "query",
        help=_("Search query."),
    )
    search_parser.add_argument(
        "--limit",
        type=int,
        default=0,
        help=_("Maximum number of results to show. Use 0 for unlimited."),
    )
    search_parser.add_argument(
        "--json",
        action="store_true",
        help=_("Output results as JSON."),
    )
    search_parser.set_defaults(func=command_search_packages)

    package_info_parser = subparsers.add_parser(
        "package-info",
        help=_("Show detailed information for one APT package."),
    )
    package_info_parser.add_argument(
        "package",
        help=_("Package name."),
    )
    package_info_parser.add_argument(
        "--json",
        action="store_true",
        help=_("Output result as JSON."),
    )
    package_info_parser.set_defaults(func=command_package_info)

    return parser


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()

    try:
        return args.func(args)
    except BrokenPipeError:
        # This happens when output is piped to tools like head/tail and the
        # reader closes the pipe before synexpm-cli finishes writing.
        try:
            devnull = os.open(os.devnull, os.O_WRONLY)
            os.dup2(devnull, sys.stdout.fileno())
        except Exception:
            pass

        return 0


if __name__ == "__main__":
    raise SystemExit(main())
