#!/usr/bin/python3

from __future__ import annotations

import errno
import fcntl
import logging
import sys
import time
from pathlib import Path
from typing import Any, NoReturn

from slpkg.all_repos import AllRepos
from slpkg.audit import Audit
from slpkg.binaries.install import Packages
from slpkg.blacklist_cmd import BlacklistCmd
from slpkg.changelog import Changelog
from slpkg.check_broken import CheckBroken
from slpkg.check_updates import CheckUpdates
from slpkg.choose_packages import Choose
from slpkg.clean_system import CleanSystem
from slpkg.cleanup import Cleanings
from slpkg.config import config_load
from slpkg.config_editor import FormConfigs
from slpkg.config_validate import ConfigValidator
from slpkg.depends import Depends
from slpkg.download_only import DownloadOnly
from slpkg.export import Export
from slpkg.file_search import FileSearch
from slpkg.health_check import HealthCheck
from slpkg.history import History
from slpkg.import_packages import ImportPackages
from slpkg.list_files import ListFiles
from slpkg.list_installed import ListInstalled
from slpkg.load_data import LoadData
from slpkg.local_install import LocalInstall
from slpkg.mark import Mark
from slpkg.multi_process import MultiProcess
from slpkg.orphans import Orphans
from slpkg.package_validator import PackageValidator
from slpkg.pin import Pin
from slpkg.rebuild import Rebuild
from slpkg.reinstall import Reinstall
from slpkg.remove_packages import RemovePackages
from slpkg.repo_info import RepoInfo
from slpkg.repo_manager import RepoManager
from slpkg.repositories import Repositories
from slpkg.reverse import Reverse
from slpkg.rollback import Rollback
from slpkg.sbo.slackbuild import Slackbuilds
from slpkg.search import SearchPackage
from slpkg.self_check import check_self_update
from slpkg.stats import Stats
from slpkg.update_repositories import UpdateRepositories
from slpkg.upgrade import Upgrade
from slpkg.utilities import Utilities
from slpkg.verify_files import VerifyFiles
from slpkg.views.display import View
from slpkg.views.package_info import InfoPackage
from slpkg.views.version import Version
from slpkg.why import Why

logger = logging.getLogger(__name__)


class Run:  # pylint: disable=[R0902,R0904]
    """Run main slpkg methods."""

    def __init__(self, options: dict[str, Any], repository: str) -> None:
        logger.debug("Initializing Run class with options: %s, repository: %s", options, repository)
        self.options = options
        self.repos = Repositories()

        self.repository = repository
        if not repository:
            self.repository = self.repos.default_repository
            logger.debug("No repository specified, defaulting to: %s", self.repository)
        else:
            logger.debug("Using specified repository: %s", self.repository)

        # Access config_load attributes directly as they are updated globally
        self.prog_name = config_load.prog_name
        self.tmp_slpkg = config_load.tmp_slpkg
        self.file_list_suffix = config_load.file_list_suffix
        self.bootloader_command = config_load.bootloader_command
        self.red = config_load.red
        self.green = config_load.green
        self.yellow = config_load.yellow
        self.endc = config_load.endc

        self.lock_file_path: Path = config_load.lock_file
        self._lock_file: object = None

        self.utils = Utilities()
        self.multi_process = MultiProcess(options=self.options)
        self.views = View(options=self.options)

        self.data: dict[str, dict[str, str]] = {}

        self.load_data = LoadData()

        self.pkg_validator = PackageValidator()
        self.choose = Choose(self.options, self.repository)
        self._acquire_lock()
        logger.debug("Run class initialization complete.")

    def _acquire_lock(self) -> None:
        """Acquire an exclusive lock for the slpkg process."""
        if self.options.get("option_no_lock", False):
            logger.debug("Locking disabled via --no-lock. Skipping lock acquisition.")
            return

        logger.debug("Attempting to acquire lock on %s", self.lock_file_path)
        try:
            # We must keep the file handle open for the entire lifetime of the process
            # to maintain the lock, so we don't use a 'with' statement here.
            self._lock_file = open(self.lock_file_path, "w", encoding="utf-8")  # pylint: disable=consider-using-with
            # Acquire an exclusive lock (LOCK_EX) without blocking (LOCK_NB).
            fcntl.flock(self._lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
            logger.info("Successfully acquired lock on %s", self.lock_file_path)
        except (IOError, OSError) as e:
            # errno.EAGAIN or errno.EACCES indicate the lock is held by someone else.
            if e.errno in {errno.EAGAIN, errno.EACCES}:
                logger.critical("Another instance of slpkg is already running. Error: %s", e)
                print(f"{self.red}Error:{self.endc} Another instance of slpkg is already running.")
                print(f"       Use {self.yellow}--no-lock{self.endc} if you want to skip the process lock.")
            else:
                logger.critical("Failed to create or acquire lock on %s: %s", self.lock_file_path, e)
                print(f"{self.red}Error:{self.endc} System error while creating lock file: {e}")
            sys.exit(1)

    def is_file_list_packages(self, packages: list[str]) -> list[str]:
        """Check for filelist.pkgs file.

        Args:
            packages (list[str]): List of packages.

        Returns:
            list[str]: List of packages for a file.
        """
        logger.debug("Checking if package list contains a file list: %s", packages)
        if packages and packages[0].endswith(self.file_list_suffix):
            file = Path(packages[0])
            logger.info("Detected package list file: %s", file)
            file_packages: list[str] = list(self.utils.read_packages_from_file(file))
            logger.debug("Packages read from file '%s': %s", file, file_packages)
            return file_packages
        logger.debug("Package list does not contain a file list. Returning original packages.")
        return packages

    def update(self) -> NoReturn:
        """Update the local repositories."""
        logger.info("Executing 'update' command.")

        if self.options.get("option_check"):
            logger.info("Running update in check mode.")
            check = CheckUpdates(self.options, self.repository)
            check.updates()
        else:
            logger.info("Starting full repository update.")
            start: float = time.time()
            update = UpdateRepositories(self.options, self.repository)
            update.repositories()
            elapsed_time: float = time.time() - start
            self.utils.finished_time(elapsed_time)
            logger.info("Repository update completed in %.2f seconds.", elapsed_time)
        sys.exit(0)

    def upgrade(self) -> NoReturn:  # pylint: disable=[R0912,R0914,R0915]
        """Upgrade the installed packages."""
        logger.info("Executing 'upgrade' command for repository: %s", self.repository)
        command: str = Run.upgrade.__name__
        removed: list[str] = []
        added: list[str] = []
        ordered: bool = True
        kernel_generic_current_package: str = self.utils.is_package_installed("kernel-generic")
        logger.debug("Current kernel-generic package: %s", kernel_generic_current_package)

        if self.options.get("option_check"):
            logger.info("Running upgrade in check mode.")
            if self.repository == "*":
                repos_to_check = AllRepos(self.options).priority_repos()
                combined: dict[str, Any] = {}
                for i, repo_name in enumerate(repos_to_check):
                    combined[repo_name] = self.load_data.load(
                        repo_name, message=not self.options.get("option_quiet") and i == 0
                    )
                Upgrade("*", combined, self.options).check_packages()
            else:
                self.data = self.load_data.load(self.repository, message=not self.options.get("option_quiet"))
                upgrade = Upgrade(self.repository, self.data, self.options)
                upgrade.check_packages()

        elif self.repository != "*":
            logger.info("Performing upgrade for specific repository: %s", self.repository)
            self.data = self.load_data.load(self.repository, message=not self.options.get("option_quiet"))
            upgrade = Upgrade(self.repository, self.data, self.options)
            packages: list[str] = list(upgrade.packages())
            logger.debug("Initial packages for upgrade: %s", packages)

            for package in packages:
                if package.endswith("_Removed."):
                    removed.append(package.replace("_Removed.", ""))
                    logger.debug("Identified package for removal: %s", package.replace("_Removed.", ""))
                if package.endswith("_Added."):
                    added.append(package.replace("_Added.", ""))
                    logger.debug("Identified package for addition: %s", package.replace("_Added.", ""))

            # Remove packages that not exists in the repository.
            if removed:
                logger.info("Initiating removal of %d packages.", len(removed))
                packages = [pkg for pkg in packages if not pkg.endswith("_Removed.")]
                remove = RemovePackages(removed, self.options)
                remove.remove(upgrade=True)
                logger.info("Packages removed during upgrade process.")

            if added:
                logger.info("Initiating addition of %d new packages.", len(added))
                packages = sorted([pkg for pkg in packages if not pkg.endswith("_Added.")])
                packages = added + packages
                ordered = False
                logger.debug("Combined added and upgradeable packages: %s", packages)

            packages = self.choose.packages(self.data, packages, command, ordered)
            logger.debug("Packages selected after user choice: %s", packages)

            if not packages:
                logger.info("No packages selected for upgrade. Exiting.")
                print("\nEverything is up-to-date!\n")
                sys.exit(0)

            if self.repository not in {self.repos.sbo_repo_name, self.repos.ponce_repo_name}:
                logger.info("Installing binary packages for upgrade.")
                install_bin = Packages(self.repository, self.data, packages, self.options, mode=command)
                install_bin.execute()
            else:
                logger.info("Installing SlackBuilds for upgrade.")
                install_sbo = Slackbuilds(self.repository, self.data, packages, self.options, mode=command)
                install_sbo.execute()

            self._is_kernel_upgrade(kernel_generic_current_package)

        else:
            # self.repository == "*": upgrade all priority-configured repositories.
            logger.info("Performing upgrade-all across priority repositories.")
            AllRepos(self.options, mode="upgrade").run()
            self._is_kernel_upgrade(kernel_generic_current_package)

        sys.exit(0)

    def _is_kernel_upgrade(self, kernel_generic_current_package: str) -> None:
        """Compare current and installed kernel package."""
        logger.debug("Checking for kernel upgrade. Old kernel-generic: %s", kernel_generic_current_package)
        kernel_generic_new_package: str = self.utils.is_package_installed("kernel-generic")
        logger.debug("New kernel-generic package: %s", kernel_generic_new_package)
        if kernel_generic_current_package != kernel_generic_new_package:
            logger.info("Kernel-generic package has been upgraded.")
            if self.bootloader_command:
                logger.info("Bootloader command is configured: '%s'. Prompting user to run.", self.bootloader_command)
                self._bootloader_update()
            else:
                logger.info("Bootloader command not configured. Displaying manual kernel update message.")
                self._kernel_image_message()
            self._reboot_prompt()

    def _kernel_image_message(self) -> None:
        """Print a warning kernel upgrade message."""
        logger.warning("Displaying kernel upgrade warning message to user.")
        print(
            f"\n{self.red}Warning!{self.endc} Your kernel image looks like to have been upgraded!\n"
            "Please update the bootloader with the new parameters of the upgraded kernel.\n"
            "See: lilo, eliloconfig or grub-mkconfig -o /boot/grub/grub.cfg,\n"
            "depending on how you have your system configured.\n"
        )

    def _reboot_prompt(self) -> None:
        """Prompt user to reboot after a kernel upgrade."""
        logger.info("Prompting user to reboot after kernel upgrade.")
        print(f"\n{self.yellow}A system reboot is required to use the new kernel.{self.endc}\n")
        if not config_load.ask_question:
            logger.info("ask_question is disabled. Skipping reboot prompt.")
            return
        try:
            answer = "y" if config_load.answer_yes else input("Do you want to reboot now? [y/N] ")
        except (KeyboardInterrupt, EOFError):
            print()
            logger.warning("Reboot prompt cancelled by user.")
            return
        if answer.lower() == "y":
            logger.info("User confirmed reboot. Executing 'reboot'.")
            self.multi_process.process("reboot")

    def _bootloader_update(self) -> None:
        """Prompt user to run bootloader update command and execute it."""
        logger.info("Prompting user for bootloader update: '%s'", self.bootloader_command)
        print(
            f"\nYour kernel image upgraded, do you want to run this command:\n"
            f"\n{self.green}    {self.bootloader_command}{self.endc}\n"
        )
        self.views.question()
        logger.info("User confirmed bootloader update. Executing command: '%s'", self.bootloader_command)
        self.multi_process.process(self.bootloader_command)
        logger.info("Bootloader update command executed.")

    def repo_info(self) -> NoReturn:
        """Print repositories information."""
        logger.info("Executing 'repo-info' command.")
        repo = RepoInfo(self.options, self.repository)
        repo.info()
        sys.exit(0)

    def repo(self) -> NoReturn:
        """Enable, disable, or list repositories."""
        logger.info("Executing 'repo' command.")
        mgr = RepoManager()
        enable_list: list[str] = self.options.get("option_repo_enable") or []
        disable_list: list[str] = self.options.get("option_repo_disable") or []
        mirror_args: list[str] = self.options.get("option_repo_mirror") or []
        default_name: str = self.options.get("option_repo_default") or ""
        priority_args: list[str] | None = self.options.get("option_repo_priority")
        clear_priority: bool = bool(self.options.get("option_repo_clear_priority"))
        quiet: bool = bool(self.options.get("option_quiet"))
        option_json: bool = bool(self.options.get("option_json"))
        if enable_list:
            mgr.enable(enable_list)
        elif disable_list:
            mgr.disable(disable_list)
        elif mirror_args:
            mgr.set_mirror(mirror_args[0], mirror_args[1])
        elif default_name:
            mgr.set_default(default_name)
        elif clear_priority:
            mgr.clear_priority()
        elif priority_args is not None:
            if priority_args:
                mgr.set_priority(priority_args)
            else:
                mgr.show_priority()
        else:
            mgr.list_repos(quiet=quiet, option_json=option_json)
        sys.exit(0)

    def stats(self) -> NoReturn:
        """Display system and repository statistics."""
        logger.info("Executing 'stats' command.")
        Stats(self.options).run()
        sys.exit(0)

    def rollback(self) -> NoReturn:
        """Undo the last package transaction."""
        logger.info("Executing 'rollback' command.")
        Rollback(self.options).run()
        sys.exit(0)

    def edit_configs(self) -> NoReturn:
        """Edit configurations via dialog box, or validate without editing."""
        logger.info("Executing 'config' command (edit configurations).")
        if self.options.get("option_validate"):
            logger.info("Running config validation.")
            ConfigValidator().run()
        form_configs = FormConfigs(options=self.options)
        form_configs.run()
        logger.info("Configuration editing completed.")
        sys.exit(0)

    def clean_tmp(self) -> NoReturn:
        """Remove all files and directories from tmp."""
        logger.info("Executing 'clean' command.")
        clean = Cleanings()
        clean.tmp()
        logger.info("Temporary files cleaned.")
        sys.exit(0)

    @staticmethod
    def self_check() -> NoReturn:
        """Check for slpkg updates."""
        logger.info("Executing 'self-check' command.")
        check_self_update()
        logger.info("Self-check completed.")
        sys.exit(0)

    def build(self, packages: list[str]) -> NoReturn:
        """Build slackbuilds with dependencies without install."""
        logger.info("Executing 'build' command for packages: %s", packages)
        command: str = Run.build.__name__

        self.data = self.load_data.load(self.repository, message=not self.options.get("option_quiet"))
        logger.debug("Loaded repository data for build command (repo: %s).", self.repository)
        build_packages = self.is_file_list_packages(packages)
        build_packages = self.utils.case_insensitive_pattern_matching(build_packages, self.data, self.options)
        logger.debug("Packages after file list and case-insensitive matching: %s", build_packages)

        if self.options.get("option_select") or self.options.get("option_dialog"):
            logger.info("User selected option_select. Prompting package selection for build.")
            build_packages = self.choose.packages(self.data, build_packages, command, ordered=True)
            logger.debug("Packages after user selection for build: %s", build_packages)

        self.pkg_validator.is_package_exists(build_packages, self.data)
        logger.debug("Existence check passed for build packages.")

        build = Slackbuilds(self.repository, self.data, build_packages, self.options, mode=command)
        logger.info("Initiating SlackBuilds build process.")
        build.execute()
        logger.info("Build command completed.")
        sys.exit(0)

    def rebuild(self) -> NoReturn:
        """Rebuild all installed SlackBuilds packages from the repository."""
        rebuild = Rebuild(self.options, self.repository)
        rebuild.run()
        sys.exit(0)

    def reinstall(self, packages: list[str]) -> NoReturn:
        """Reinstall installed packages from their source repository."""
        logger.info("Executing 'reinstall' command for packages: %s", packages)
        packages = self.is_file_list_packages(packages)
        Reinstall(packages, self.options).run()
        logger.info("Reinstall command completed.")
        sys.exit(0)

    def install(self, packages: list[str], exit_after: bool = True) -> None:
        """Build and install packages with dependencies."""
        logger.info("Executing 'install' command for packages: %s", packages)
        command: str = Run.install.__name__
        kernel_generic_current_package: str = self.utils.is_package_installed("kernel-generic")
        logger.debug("Current kernel-generic package: %s", kernel_generic_current_package)

        if self.repository == "*":
            logger.info("Performing install-all across priority repositories.")
            AllRepos(self.options, packages=packages, mode="install").run()
            self._is_kernel_upgrade(kernel_generic_current_package)
            if exit_after:
                sys.exit(0)
            return

        self.data = self.load_data.load(self.repository, message=not self.options.get("option_quiet"))
        logger.debug("Loaded repository data for install command (repo: %s).", self.repository)
        install_packages = self.is_file_list_packages(packages)
        install_packages = self.utils.case_insensitive_pattern_matching(install_packages, self.data, self.options)
        logger.debug("Packages after file list and case-insensitive matching: %s", install_packages)

        if self.options.get("option_select") or self.options.get("option_dialog"):
            logger.info("User selected option_select. Prompting package selection for install.")
            install_packages = self.choose.packages(self.data, install_packages, command, ordered=True)
            logger.debug("Packages after user selection for install: %s", install_packages)

        self.pkg_validator.is_package_exists(install_packages, self.data)
        logger.debug("Existence check passed for install packages.")

        if self.repository not in {self.repos.sbo_repo_name, self.repos.ponce_repo_name}:
            logger.info("Installing binary packages.")
            install_bin = Packages(self.repository, self.data, install_packages, self.options, mode=command)
            install_bin.execute()
        else:
            logger.info("Installing SlackBuilds.")
            install_sbo = Slackbuilds(self.repository, self.data, install_packages, self.options, mode=command)
            install_sbo.execute()

        self._is_kernel_upgrade(kernel_generic_current_package)
        logger.info("Install command completed.")
        if exit_after:
            sys.exit(0)

    def install_pkg(self, packages: list[str]) -> NoReturn:
        """Install one or more local Slackware package files."""
        logger.info("Executing 'install-pkg' command for files: %s", packages)

        local_install = LocalInstall(self.options, packages)
        local_install.run()
        logger.info("Install-pkg command completed.")
        sys.exit(0)

    def remove(self, packages: list[str]) -> NoReturn:
        """Remove packages with dependencies."""
        logger.info("Executing 'remove' command for packages: %s", packages)
        command: str = Run.remove.__name__

        remove_packages: list[str] = self.is_file_list_packages(packages)
        logger.debug("Packages after file list check for remove: %s", remove_packages)

        if self.options.get("option_select") or self.options.get("option_dialog"):
            logger.info("User selected option_select. Prompting package selection for remove.")
            remove_packages = self.choose.packages(
                {}, remove_packages, command, ordered=True
            )  # No data needed for remove choose.
            logger.debug("Packages after user selection for remove: %s", remove_packages)

        self.pkg_validator.is_package_installed(remove_packages)
        logger.debug("Installed check passed for remove packages.")

        remove = RemovePackages(remove_packages, self.options)
        logger.info("Initiating package removal process.")
        remove.remove()
        logger.info("Remove command completed.")
        sys.exit(0)

    def download(self, packages: list[str], directory: str) -> NoReturn:
        """Download only packages."""
        logger.info("Executing 'download' command for packages: %s to directory: %s", packages, directory)
        command: str = Run.download.__name__

        if not directory:
            directory = str(self.tmp_slpkg)
            logger.debug("No download directory specified, defaulting to: %s", directory)
        else:
            logger.debug("Using specified download directory: %s", directory)

        self.data = self.load_data.load(self.repository, message=not self.options.get("option_quiet"))
        logger.debug("Loaded repository data for download command (repo: %s).", self.repository)
        download_packages = self.is_file_list_packages(packages)
        download_packages = self.utils.case_insensitive_pattern_matching(download_packages, self.data, self.options)
        logger.debug("Packages after file list and case-insensitive matching: %s", download_packages)

        if self.options.get("option_select") or self.options.get("option_dialog"):
            logger.info("User selected option_select. Prompting package selection for download.")
            download_packages = self.choose.packages(self.data, download_packages, command, ordered=True)
            logger.debug("Packages after user selection for download: %s", download_packages)

        self.pkg_validator.is_package_exists(download_packages, self.data)
        logger.debug("Existence check passed for download packages.")

        down_only = DownloadOnly(directory, self.options, self.data, self.repository)
        logger.info("Initiating package download process.")
        down_only.packages(download_packages)
        logger.info("Download command completed.")
        sys.exit(0)

    def export(self, filename: str) -> NoReturn:
        """Export manually installed packages to a file."""
        logger.info("Executing 'export' command.")
        Export(self.options).run(filename)
        logger.info("Export command completed.")
        sys.exit(0)

    def import_packages(self, filename: str) -> NoReturn:
        """Import and install packages from a file."""
        logger.info("Executing 'import' command with file: %s", filename)
        plan = ImportPackages(self.options).run(filename)

        if not plan:
            sys.exit(0)

        for repo, packages in plan:
            logger.info("Import: Installing %d packages from repository '%s'", len(packages), repo)
            self.repository = repo
            # Pass exit_after=False to continue the loop
            self.install(packages, exit_after=False)

        logger.info("Import command completed successfully.")
        sys.exit(0)

    def list_installed(self, packages: list[str]) -> NoReturn:
        """Find installed packages."""
        logger.info("Executing 'list' command for packages: %s", packages)
        command: str = Run.list_installed.__name__

        ls_packages: list[str] = self.is_file_list_packages(packages or ["*"])
        logger.debug("Packages after file list check for list: %s", ls_packages)

        if self.options.get("option_select") or self.options.get("option_dialog"):
            logger.info("User selected option_select. Prompting package selection for list.")
            data: dict[str, dict[str, str]] = {}  # No repository data needed for installed packages.
            ls_packages = self.choose.packages(data, ls_packages, command, ordered=True)
            logger.debug("Packages after user selection for list: %s", ls_packages)

        ls = ListInstalled(self.options, ls_packages)
        logger.info("Initiating listing of installed packages.")
        ls.installed()
        logger.info("List command completed.")
        sys.exit(0)

    def info_package(self, packages: list[str]) -> NoReturn:
        """View package information."""
        logger.info("Executing 'info' command for packages: %s (repo: %s)", packages, self.repository)
        command: str = Run.info_package.__name__

        self.data = self.load_data.load(self.repository, message=not self.options.get("option_quiet"))
        logger.debug("Loaded repository data for info command (repo: %s).", self.repository)
        info_packages = self.is_file_list_packages(packages)
        info_packages = self.utils.case_insensitive_pattern_matching(info_packages, self.data, self.options)
        logger.debug("Packages after file list and case-insensitive matching: %s", info_packages)

        if self.options.get("option_select") or self.options.get("option_dialog"):
            logger.info("User selected option_select. Prompting package selection for info.")
            info_packages = self.choose.packages(self.data, info_packages, command, ordered=True)
            logger.debug("Packages after user selection for info: %s", info_packages)

        self.pkg_validator.is_package_exists(info_packages, self.data)
        logger.debug("Existence check passed for info packages.")

        view = InfoPackage(self.options, self.repository)

        if self.repository not in {self.repos.sbo_repo_name, self.repos.ponce_repo_name}:
            logger.info("Displaying binary package information.")
            view.package(self.data, info_packages)
        else:
            logger.info("Displaying SlackBuild package information.")
            view.slackbuild(self.data, info_packages)
        logger.info("Info command completed.")
        sys.exit(0)

    def search(self, packages: list[str]) -> NoReturn:
        """Search packages from the repositories."""
        logger.info("Executing 'search' command for packages: %s (repo: %s)", packages, self.repository)
        command: str = Run.search.__name__
        self.data = self.load_data.load(self.repository, message=not self.options.get("option_quiet"))
        logger.debug("Loaded repository data for search command (repo: %s).", self.repository)

        search_packages: list[str] = self.is_file_list_packages(packages)
        logger.debug("Packages after file list check for search: %s", search_packages)

        if self.options.get("option_select") or self.options.get("option_dialog"):
            logger.info("User selected option_select. Prompting package selection for search.")
            search_packages = self.choose.packages(self.data, search_packages, command, ordered=True)
            logger.debug("Packages after user selection for search: %s", search_packages)

        pkgs = SearchPackage(self.options, search_packages, self.data, self.repository)
        logger.info("Initiating package search process.")
        pkgs.search()
        logger.info("Search command completed.")
        sys.exit(0)

    def file_search(self, files: list[str]) -> NoReturn:
        """Search packages from the repositories."""
        logger.info("Executing 'file-search' command for packages: %s (repo: %s)", files, self.repository)

        file_search = FileSearch(self.options, files, self.repository)
        logger.info("Initiating files search process.")
        file_search.run()
        logger.info("File Search command completed.")
        sys.exit(0)

    def list_files(self, packages: list[str]) -> NoReturn:
        """List files installed by a package."""
        logger.info("Executing 'list-files' command for packages: %s", packages)

        list_files = ListFiles(self.options, packages)
        list_files.run()
        logger.info("List Files command completed.")
        sys.exit(0)

    def reverse(self, packages: list[str]) -> NoReturn:
        """View packages that depend on (require) the specified package."""
        logger.info("Executing 'reverse' command for packages: %s (repo: %s)", packages, self.repository)
        command: str = Run.reverse.__name__

        self.data = self.load_data.load(self.repository, message=not self.options.get("option_quiet"))
        logger.debug("Loaded repository data for reverse command (repo: %s).", self.repository)
        reverse_packages = self.is_file_list_packages(packages)
        reverse_packages = self.utils.case_insensitive_pattern_matching(reverse_packages, self.data, self.options)
        logger.debug("Packages after file list and case-insensitive matching: %s", reverse_packages)

        if self.options.get("option_select") or self.options.get("option_dialog"):
            logger.info("User selected option_select. Prompting package selection for reverse.")
            reverse_packages = self.choose.packages(self.data, reverse_packages, command, ordered=True)
            logger.debug("Packages after user selection for reverse: %s", reverse_packages)

        self.pkg_validator.is_package_exists(reverse_packages, self.data)
        logger.debug("Existence check passed for reverse packages.")

        dependees = Reverse(self.data, reverse_packages, self.options)
        logger.info("Initiating reverse dependency find process.")
        dependees.find()
        logger.info("Reverse command completed.")
        sys.exit(0)

    def depends(self, packages: list[str]) -> NoReturn:
        """Display the full dependency tree for the specified packages."""
        logger.info("Executing 'depends' command for packages: %s (repo: %s)", packages, self.repository)
        command: str = Run.depends.__name__

        self.data = self.load_data.load(self.repository, message=not self.options.get("option_quiet"))
        logger.debug("Loaded repository data for depends command (repo: %s).", self.repository)
        depends_packages = self.is_file_list_packages(packages)
        depends_packages = self.utils.case_insensitive_pattern_matching(depends_packages, self.data, self.options)
        logger.debug("Packages after file list and case-insensitive matching: %s", depends_packages)

        if self.options.get("option_select") or self.options.get("option_dialog"):
            logger.info("User selected option_select. Prompting package selection for depends.")
            depends_packages = self.choose.packages(self.data, depends_packages, command, ordered=True)
            logger.debug("Packages after user selection for depends: %s", depends_packages)

        self.pkg_validator.is_package_exists(depends_packages, self.data)
        logger.debug("Existence check passed for depends packages.")

        tracking = Depends(self.data, depends_packages, self.options, self.repository)
        logger.info("Initiating package dependency tree process.")
        tracking.package()
        logger.info("Depends command completed.")
        sys.exit(0)

    def changelog_print(self, query: str) -> NoReturn:
        """Prints repository changelog."""
        logger.info("Executing 'changelog' command for query: '%s' (repo: %s)", query, self.repository)
        changelog_manager = Changelog(options=self.options, repository=self.repository, query=query)
        changelog_manager.run()
        logger.info("Changelog command completed.")
        sys.exit(0)

    @staticmethod
    def version() -> NoReturn:
        """Print program version and exit."""
        logger.info("Executing 'version' command.")
        version = Version()
        version.view()
        logger.info("Version command completed.")
        sys.exit(0)

    def verify(self, packages: list[str]) -> NoReturn:
        """Verify integrity of installed packages."""
        logger.info("Executing 'verify' command for packages: %s", packages)
        if packages:
            self.pkg_validator.is_package_installed(packages)
        broken = VerifyFiles(self.options, packages).check()
        if broken and self.options.get("option_fix"):
            logger.info("Fix requested: reinstalling %d broken package(s): %s", len(broken), ", ".join(broken))
            Reinstall(broken, self.options).run()
        logger.info("Verify command completed.")
        sys.exit(0)

    def show_history(self) -> NoReturn:
        """Display package operation history."""
        logger.info("Executing 'history' command.")
        if self.options.get("option_clear_history"):
            History().clear()
        else:
            limit: int = self.options.get("option_lines", 0)
            package: str = self.options.get("option_history_package", "")
            option_json: bool = self.options.get("option_json", False)
            History(limit=limit, package=package, option_json=option_json).view()
        logger.info("history command completed.")
        sys.exit(0)

    def find_orphans(self) -> NoReturn:
        """List installed packages no longer needed as dependencies."""
        logger.info("Executing 'orphans' command.")
        if self.options.get("option_autoremove"):
            orphans = Orphans(self.options).find()
            if not orphans:
                print("No orphaned packages to remove.")
                sys.exit(0)
            RemovePackages(orphans, self.options).remove()
        else:
            Orphans(self.options).view()
        logger.info("Orphans command completed.")
        sys.exit(0)

    def mark(self, packages: list[str]) -> NoReturn:
        """Mark or unmark packages as manually installed."""
        logger.info("Executing 'mark' command for: %s", ", ".join(packages))
        if packages and not self.options.get("option_unmark"):
            self.pkg_validator.is_package_installed(packages)
        Mark(packages, self.options).view()
        logger.info("Mark command completed.")
        sys.exit(0)

    def why(self, packages: list[str]) -> NoReturn:
        """Show which installed packages depend on the given package(s)."""
        logger.info("Executing 'why' command for: %s", ", ".join(packages))
        if "*" not in packages:
            self.pkg_validator.is_package_installed(packages)
        Why(packages, self.options).view()
        logger.info("Why command completed.")
        sys.exit(0)

    def blacklist(self, packages: list[str]) -> NoReturn:
        """Display, add to, or remove from the blacklist."""
        logger.info("Executing 'blacklist' command.")
        cmd = BlacklistCmd(self.options)
        if self.options.get("option_blacklist_remove"):
            cmd.remove(packages)
        elif packages:
            cmd.add(packages)
        else:
            cmd.view()
        logger.info("Blacklist command completed.")
        sys.exit(0)

    def pin(self, packages: list[str]) -> NoReturn:
        """Pin or unpin packages to exclude them from upgrades."""
        logger.info("Executing 'pin' command for: %s", ", ".join(packages))
        if packages and not self.options.get("option_unpin"):
            self.pkg_validator.is_package_installed(packages)
        Pin(packages, self.options).view()
        logger.info("Pin command completed.")
        sys.exit(0)

    def check_broken(self) -> NoReturn:
        """Check installed packages for missing tracked dependencies."""
        logger.info("Executing 'check-broken' command.")
        CheckBroken(self.options).run()
        logger.info("Check-broken command completed.")
        sys.exit(0)

    def clean_system(self) -> NoReturn:
        """Find and remove foreign packages."""
        logger.info("Executing 'clean-system' command.")
        CleanSystem(self.options).run()
        logger.info("Clean-system command completed.")
        sys.exit(0)

    def health_check(self, packages: list[str]) -> NoReturn:
        """Check system for broken shared library links."""
        logger.info("Executing 'health-check' command for packages: %s", packages)
        HealthCheck(self.options, packages).run()
        logger.info("Health-check command completed.")
        sys.exit(0)

    def audit(self, packages: list[str]) -> NoReturn:
        """Audit installed packages for security vulnerabilities."""
        logger.info("Executing 'audit' command for packages: %s", packages)
        Audit(self.options, packages).run()
        logger.info("Audit command completed.")
        sys.exit(0)
