#!/usr/bin/python3


from __future__ import annotations

import json
import logging
from typing import Union, cast

from slpkg.config import config_load
from slpkg.repositories import Repositories
from slpkg.utilities import Utilities
from slpkg.views.process import ViewProcess

logger = logging.getLogger(__name__)


class SearchPackage:  # pylint: disable=[R0902]
    """Search packages from the repositories."""

    def __init__(
        self,
        options: dict[str, bool],
        packages: list[str],
        data: Union[dict[str, dict[str, dict[str, str]]], dict[str, dict[str, str]]],
        repository: str,
    ) -> None:
        logger.debug(
            "Initializing SearchPackage module with %d packages, repository: %s, options: %s",
            len(packages),
            repository,
            options,
        )
        self.packages = packages
        self.data = data
        self.repository = repository

        self.grey = config_load.grey
        self.green = config_load.green
        self.yellow = config_load.yellow
        self.endc = config_load.endc

        self.utils = Utilities()
        self.repos = Repositories()
        self.view_process = ViewProcess()

        self.matching: int = 0
        self.data_dict: dict[int, dict[str, str]] = {}
        self.repo_data: Union[dict[str, dict[str, dict[str, str]]], dict[str, dict[str, str]]] = {}
        self.all_data: Union[dict[str, dict[str, dict[str, str]]], dict[str, dict[str, str]]] = {}

        self.option_for_no_case: bool = options.get("option_no_case", False)
        self.option_for_pkg_version: bool = options.get("option_pkg_version", False)
        self.option_for_pkg_description: bool = options.get("option_pkg_description", False)
        self.option_quiet: bool = options.get("option_quiet", False)
        self.option_select: bool = options.get("option_select", False)
        self.option_dialog: bool = options.get("option_dialog", False)
        self.option_json: bool = options.get("option_json", False)
        logger.debug(
            "SearchPackage module initialized. Case-insensitive: %s, Show version: %s, Show description: %s, Quiet: %s",
            self.option_for_no_case,
            self.option_for_pkg_version,
            self.option_for_pkg_description,
            self.option_quiet,
        )

    def search(self) -> None:
        """Choose between searching all repositories or a specific one."""
        logger.info(
            "Starting package search for packages: %s in repository: %s", ", ".join(self.packages), self.repository
        )
        if not self.option_quiet:
            self.view_process.message("Searching")  # Console message.

        if self.repository == "*":
            logger.debug("Searching across all enabled repositories.")
            self.search_to_all_repositories()
        else:
            logger.debug("Searching in specific repository: %s", self.repository)
            self.repo_data = self.data
            self.search_for_the_packages(self.repository)

        if not self.option_quiet:
            self.view_process.done()  # Console message
            print()  # Console formatting

        self.summary_of_searching()
        logger.info("Package search completed. Total matches found: %d", self.matching)

    def search_to_all_repositories(self) -> None:
        """Search package name to all enabled repositories."""
        logger.debug("Iterating through all repositories for search.")
        self.all_data = self.data  # self.data contains all repositories when '*' is used.
        for repo_name, repo_content in self.all_data.items():
            logger.debug("Searching in repository: %s", repo_name)
            self.repo_data = cast(
                dict[str, dict[str, str]], repo_content
            )  # Set current repo's data for search_for_the_packages.
            self.search_for_the_packages(repo_name)

    def _is_match(self, package: str, name: str) -> bool:
        """Return True if the search query matches the repository package name.

        Args:
            package (str): Search query supplied by the user.
            name (str): Package name from the repository.

        Returns:
            bool: True if the package is a match.
        """
        if package in ("*", name):
            return True
        if not (self.option_select or self.option_dialog) and (
            package in name or self.is_not_case_sensitive(package, name)
        ):
            return True
        return False

    def _get_installed_status(self, name: str, data_pkg: dict[str, str]) -> str:
        """Return the installed-status label for a matched package.

        Compares the installed package filename against the repository entry to
        determine whether the currently installed version matches this repo's version.

        Args:
            name (str): Package name from the repository.
            data_pkg (dict): Repository data entry for the package.

        Returns:
            str: Coloured '[installed]' label, or empty string if not installed / version mismatch.
        """
        is_installed_full_name: str = self.utils.is_package_installed(name)
        if not is_installed_full_name:
            logger.debug("Package '%s' is not currently installed.", name)
            return ""

        if self.repository == "*":
            if is_installed_full_name == data_pkg.get("package", "")[:-4]:
                logger.debug("Package '%s' is installed and matches this repo's version (all repos search).", name)
                return f"[{self.green}installed{self.endc}]"
            logger.debug("Package '%s' is installed but does not match this repo's version (all repos search).", name)
        else:
            single_repo_data: dict[str, dict[str, str]] = cast(dict[str, dict[str, str]], self.data)
            if is_installed_full_name == single_repo_data.get(name, {}).get("package", "")[:-4]:
                logger.debug("Package '%s' is installed and matches this repo's version (single repo search).", name)
                return f"[{self.green}installed{self.endc}]"
            logger.debug(
                "Package '%s' is installed but does not match this repo's version (single repo search).", name
            )
        return ""

    def search_for_the_packages(self, repo: str) -> None:
        """Search for packages within a given repository and save matching data.

        Args:
            repo (str): repository name.
        """
        for name, data_pkg in sorted(self.repo_data.items()):
            for package in self.packages:
                if not self._is_match(package, name):
                    continue

                self.matching += 1
                logger.debug(
                    "Match found for query '%s' in package '%s' (Repo: %s). Match count: %d",
                    package, name, repo, self.matching,
                )

                if not isinstance(data_pkg, dict):
                    logger.warning(
                        "Unexpected data_pkg format for package '%s' in repo '%s'. Expected dict, got %s. Skipping.",
                        name, repo, type(data_pkg),
                    )
                    break

                self.data_dict[self.matching] = {
                    "repository": repo,
                    "name": name,
                    "version": data_pkg.get("version", "N/A"),
                    "installed": self._get_installed_status(name, data_pkg),
                }
                logger.debug("Stored match: %s", self.data_dict[self.matching])
                break

    def _print_package_line(self, item: dict[str, str], name_length: int, repo_length: int) -> None:
        """Print the display line(s) for a single matched package.

        Args:
            item (dict): Matched package entry from data_dict.
            name_length (int): Column width for the package name field.
            repo_length (int): Column width for the repository name field.
        """
        name: str = item["name"]
        package_name_display: str = f"{name} {item['installed']}"

        version_display: str = item["version"] if self.option_for_pkg_version else ""

        repository_display: str = ""
        if self.repository == "*":
            repository_display = f"{item['repository']:<{repo_length}} : "

        description_display: str = ""
        if self.option_for_pkg_description and self.repository != "*":
            single_repo_data: dict[str, dict[str, str]] = cast(dict[str, dict[str, str]], self.data)
            if name in single_repo_data:
                description_display = single_repo_data[name].get("description", "No description available.")
            else:
                description_display = "Description not available for this package/repo."
                logger.warning(
                    "Could not retrieve description for package '%s' (repo: %s).", name, self.repository
                )
            package_name_display = f"{name}: {item['installed']}" if item["installed"] else name

        print(f"{repository_display}{package_name_display:<{name_length}} {version_display}")
        if description_display:
            print(f"  {self.yellow}{description_display}{self.endc}")

    def summary_of_searching(self) -> None:
        """Print the search results summary to the console."""
        logger.info("Generating search results summary. Total matches: %d", self.matching)

        if self.option_json:
            results = [
                {
                    "repository": item["repository"],
                    "name": item["name"],
                    "version": item["version"],
                    "installed": bool(item["installed"]),
                }
                for item in self.data_dict.values()
            ]
            print(json.dumps(results, indent=2))
            return

        if not self.matching:
            if not self.option_quiet:
                print("Does not match any package.")
            logger.info("No packages matched the search criteria.")
            return

        if self.option_quiet:
            for item in self.data_dict.values():
                print(item["name"])
            return

        repo_length: int = max((len(i["repository"]) for i in self.data_dict.values()), default=1)
        name_length: int = max(
            (len(i["name"]) + (len("[installed]") if i["installed"] else 0) for i in self.data_dict.values()),
            default=1,
        )
        logger.debug("Calculated display lengths: repo_length=%d, name_length=%d", repo_length, name_length)

        for item in self.data_dict.values():
            self._print_package_line(item, name_length, repo_length)

        print(f"\n{self.grey}Found {self.matching} packages.{self.endc}")
        logger.info("Search summary displayed to console. Total matches: %d", self.matching)

    def is_not_case_sensitive(self, package_query: str, repo_package_name: str) -> bool:
        """Check for case-insensitive match.

        Args:
            package_query (str): The package name queried by the user.
            repo_package_name (str): The package name from the repository.

        Returns:
            bool: True if a case-insensitive match is found and option is enabled, False otherwise.
        """
        if self.option_for_no_case:
            # Log the case-insensitive comparison for debugging
            logger.debug(
                "Performing case-insensitive comparison: '%s' in '%s'", package_query.lower(), repo_package_name.lower()
            )
            return package_query.lower() in repo_package_name.lower()
        return False
