#!/usr/bin/python3


from __future__ import annotations

import logging
from collections.abc import Iterator
from typing import Optional, cast

from slpkg.config import config_load
from slpkg.dialog_box import DialogBox
from slpkg.terminal_selector import TerminalSelector
from slpkg.utilities import Utilities

logger = logging.getLogger(__name__)


class Choose:  # pylint: disable=[R0902]
    """
    Choose packages with dialog or with terminal selector.
    """

    def __init__(self, options: dict[str, bool], repository: str = "") -> None:
        logger.debug("Initializing Choose module with repository: '%s', options: %s", repository, options)
        self.repository = repository
        self.options = options

        self.dialog = config_load.dialog
        self.bold = config_load.bold
        self.green = config_load.green
        self.red = config_load.red
        self.endc = config_load.endc
        self.clear_screen = config_load.clear_screen

        self.utils = Utilities()
        self.dialogbox = DialogBox()

        self.choices: list[tuple[str, str, bool, str]] = []
        self.height: int = 10
        self.width: int = 70
        self.list_height: int = 0
        self.match_packages: list[str] = []

        self.option_for_select_packages: bool = options.get("option_select", False)
        logger.debug(
            "Choose module initialized. Dialog enabled: %s, Option for select packages: %s",
            self.dialog,
            self.option_for_select_packages,
        )

    def packages(
        self, data: dict[str, dict[str, str]], packages: list[str], method: str, ordered: bool = True
    ) -> list[str]:
        """Call methods to choosing packages via dialog tool.

        Args:
            data (dict[str, dict[str, str]]): Repository data.
            packages (list[str]): List of packages.
            method (str): Type of method (e.g., 'remove', 'upgrade', 'install').
            ordered (bool, optional): Set True for ordered. Defaults to True.

        Returns:
            list[str]: Name of packages selected by the user.

        Raises:
            SystemExit: Exit code 0 if user cancels selection.
        """
        logger.info("Starting package selection for method: '%s', %d initial packages.", method, len(packages))
        title: str = f" Choose packages you want to {method} "
        is_upgrade = method == "upgrade"

        if method in ("remove", "list_installed"):
            logger.debug("Method is '%s'. Calling choose_from_installed.", method)
            self.choose_from_installed(packages)
        elif is_upgrade:
            title = f" Choose packages you want to {method} or add "
            logger.debug("Method is 'upgrade'. Calling choose_for_upgraded.")
            self.choose_for_upgraded(data, packages)
        else:
            logger.debug("Method is '%s'. Calling choose_for_others.", method)
            self.choose_for_others(data, packages)

        if not self.choices:
            logger.info("No choices generated for selection. Returning original packages list.")
            return packages

        return self._run_selector(title, data, is_upgrade, ordered)

    def _run_selector(
        self, title: str, data: dict[str, dict[str, str]], is_upgrade: bool, ordered: bool
    ) -> list[str]:
        """Dispatch to dialog or terminal selector.

        Args:
            title (str): Title for the selector UI.
            data (dict[str, dict[str, str]]): Repository data.
            is_upgrade (bool): Whether the operation is an upgrade.
            ordered (bool): Sort packages before display.

        Returns:
            list[str]: Selected packages.
        """
        if self.dialog:
            logger.info("Using dialogbox for package selection.")
            return self._select_via_dialog(title, ordered)
        logger.info("Using TerminalSelector for package selection.")
        return self._select_via_terminal(title, data, is_upgrade, ordered)

    def _select_via_dialog(self, title: str, ordered: bool) -> list[str]:
        """Present package selection via dialog checklist.

        Args:
            title (str): Dialog window title.
            ordered (bool): Sort choices before display.

        Returns:
            list[str]: Selected packages.

        Raises:
            SystemExit: Exit code 0 if user cancels or selects nothing.
        """
        if ordered:
            self.choices = sorted(self.choices)
            logger.debug("Choices sorted for dialog.")

        text: str = f"There are {len(self.choices)} packages:"
        code, selected = self.dialogbox.checklist(
            text, title, self.height, self.width, self.list_height, self.choices
        )
        print(self.clear_screen, end="", flush=True)

        if code == "cancel" or not selected:
            logger.info("Dialogbox selection cancelled or no packages selected. Exiting.")
            raise SystemExit(0)

        logger.info("Dialogbox returned %d selected packages.", len(selected))
        return selected

    def _select_via_terminal(
        self, title: str, data: dict[str, dict[str, str]], is_upgrade: bool, ordered: bool
    ) -> list[str]:
        """Present package selection via terminal selector.

        Args:
            title (str): Selector window title.
            data (dict[str, dict[str, str]]): Repository data.
            is_upgrade (bool): Whether the operation is an upgrade.
            ordered (bool): Sort packages before display.

        Returns:
            list[str]: Selected packages.
        """
        if ordered:
            self.match_packages = sorted(self.match_packages)
            logger.debug("Packages sorted for terminal selector.")

        initial_selection: Optional[str] = None if self.option_for_select_packages else "all"
        if self.option_for_select_packages:
            logger.debug("Option --select enabled, initial TerminalSelector selection set to None.")

        selected = TerminalSelector(
            self.match_packages, title, data, is_upgrade, initial_selection=initial_selection
        ).select()
        logger.info("TerminalSelector returned %d selected packages.", len(selected))
        return selected

    def choose_from_installed(self, packages: list[str]) -> None:
        """Choose installed packages for remove or find.

        Args:
            packages (list[str]): Name of packages.
        """
        logger.debug("Choosing from installed packages for removal/find. Input packages: %s", ", ".join(packages))
        all_installed_packages = self.utils.all_installed()
        sorted_queries = sorted(packages)
        seen: set[str] = set()

        for pkg_name_from_installed, full_installed_package in all_installed_packages.items():
            version: str = self.utils.split_package(full_installed_package)["version"]

            for pkg_query in sorted_queries:
                if pkg_query in full_installed_package or pkg_query == "*":
                    if pkg_name_from_installed not in seen:
                        seen.add(pkg_name_from_installed)
                        self.match_packages.append(pkg_name_from_installed)
                        self.choices.append(
                            (pkg_name_from_installed, version, False, f"Package: {full_installed_package}")
                        )
                        logger.debug(
                            "Added installed package '%s' (version: %s) to choices.", pkg_name_from_installed, version
                        )
                    else:
                        logger.debug(
                            "Installed package '%s' already in choices. Skipping duplicate.", pkg_name_from_installed
                        )
        logger.debug("Finished choosing from installed packages. Total choices: %d", len(self.choices))

    def choose_for_upgraded(self, data: dict[str, dict[str, str]], packages: list[str]) -> None:
        """Choose packages that they will going to upgrade.

        Args:
            data (dict[str, dict[str, str]]): Data of repository.
            packages (list[str]): Name of packages.
        """
        logger.debug("Choosing packages for upgrade. Input packages: %s", ", ".join(packages))
        for package_name in packages:
            plain_name = package_name.rsplit(" [", 1)[0] if " [" in package_name else package_name
            inst_package_full_name: str = self.utils.is_package_installed(plain_name)

            if package_name not in data:
                logger.warning(
                    "Package '%s' not found in repository data for upgrade selection. Skipping.", package_name
                )
                continue

            repo_ver: str = data[package_name]["version"]
            repo_build_tag: str = data[package_name]["build"]

            if not inst_package_full_name:
                new_package_full_name: str = data[package_name]["package"]
                self.match_packages.append(package_name)
                self.choices.append(
                    (
                        package_name,
                        " <- \\Z1Add\\Zn New Package ",
                        True,  # \\Z1Add\\Zn is dialog color code.
                        f"Add new package -> {new_package_full_name} Build: {repo_build_tag}",
                    )
                )
                logger.debug("Added new package '%s' to upgrade choices.", package_name)
            else:
                inst_pkg = self.utils.split_package(inst_package_full_name)
                inst_package_version: str = inst_pkg["version"]
                inst_package_build: str = inst_pkg["build"]

                self.match_packages.append(package_name)
                self.choices.append(
                    (
                        package_name,
                        f" {inst_package_version} -> \\Z3\\Zb{repo_ver}\\Zn ",
                        True,  # \\Z3\\Zb is dialog color code.
                        f"Installed: {package_name}-{inst_package_version} Build: {inst_package_build} -> "
                        f"Available: {repo_ver} Build: {repo_build_tag}",
                    )
                )
                logger.debug(
                    "Added upgradeable package '%s' to upgrade choices (Installed: %s, Repo: %s).",
                    package_name,
                    inst_package_version,
                    repo_ver,
                )
        logger.debug("Finished choosing packages for upgrade. Total choices: %d", len(self.choices))

    def _iter_packages(self, data: dict[str, dict[str, str]]) -> Iterator[tuple[str, str, str]]:
        """Yield (package_name, version, repo_name) for all packages in data.

        Handles both multi-repository ('*') and single-repository modes.

        Args:
            data (dict[str, dict[str, str]]): Repository data.

        Yields:
            tuple[str, str, str]: Package name, version, repository name.
        """
        if self.repository == "*":
            multi_data = cast(dict[str, dict[str, dict[str, str]]], data)
            for repo_name, repo_data in multi_data.items():
                for package_name, pkg_details in repo_data.items():
                    yield package_name, pkg_details.get("version", "N/A"), repo_name
        else:
            for package_name, pkg_details in data.items():
                yield package_name, pkg_details.get("version", "N/A"), self.repository

    def choose_for_others(self, data: dict[str, dict[str, str]], packages: list[str]) -> None:
        """Choose packages for others methods like install, tracking etc.

        Args:
            data (dict[str, dict[str, str]]): Repository data (can be multi-repo or single-repo data).
            packages (list[str]): Name of packages.
        """
        logger.debug("Choosing packages for other methods (install/tracking). Input packages: %s", ", ".join(packages))
        sorted_queries = sorted(packages)
        seen: set[str] = set()

        for package_name, version, repo_name in self._iter_packages(data):
            for pkg_query in sorted_queries:
                if pkg_query in package_name or pkg_query == "*":
                    if package_name not in seen:
                        seen.add(package_name)
                        self.match_packages.append(package_name)
                        self.choices.append(
                            (package_name, version, False, f"Package: {package_name}-{version} > {repo_name}")
                        )
                        logger.debug(
                            "Added choice for '%s' (version: %s, repo: %s).", package_name, version, repo_name
                        )
                    else:
                        logger.debug("Package '%s' already in choices. Skipping duplicate.", package_name)
        logger.debug("Finished choosing packages for other methods. Total choices: %d", len(self.choices))
