#!/usr/bin/python3


from __future__ import annotations

import logging
from typing import Any, Optional, cast

from slpkg.config import config_load
from slpkg.tree import collect_transitive_names, render_tree
from slpkg.utilities import Utilities

logger = logging.getLogger(__name__)


class Depends:  # pylint: disable=[R0902]
    """Tracking of the package dependencies (depends command)."""

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

        self.view_missing_deps = config_load.view_missing_deps
        self.bold = config_load.bold
        self.grey = config_load.grey
        self.yellow = config_load.yellow
        self.red = config_load.red
        self.cyan = config_load.cyan
        self.green = config_load.green
        self.endc = config_load.endc
        self.unicode = config_load.unicode

        self.utils = Utilities()

        self.package_version: str = ""
        self.package_dependency_version: str = ""
        self.package_line: str = ""
        self.require_line: str = ""
        self.require_length: int = 0

        self.option_for_pkg_version: bool = options.get("option_pkg_version", False)
        self.option_quiet: bool = options.get("option_quiet", False)
        logger.debug(
            "Tracking module initialized. View missing deps: %s, Option for package version: %s",
            self.view_missing_deps,
            self.option_for_pkg_version,
        )

    def package(self) -> None:
        """Print the dependency tree for each requested package."""
        logger.info("Starting package dependency tracking for %d packages.", len(self.packages))
        self.packages = self.utils.apply_package_pattern(self.data, self.packages)

        def color_fn(name: str) -> str:
            return self.red if name not in self.data else ""

        label_fn = None
        if self.option_for_pkg_version:

            def get_label(name: str) -> str:
                ver = self.data.get(name, {}).get("version", "")
                return f"{name} {self.yellow}{ver}{self.endc}" if ver else name

            label_fn = get_label

        for package in self.packages:
            tree = self._build_dep_tree(package)

            if self.option_quiet:
                all_deps = set(tree.keys()) | collect_transitive_names(tree)
                for name in sorted(all_deps):
                    print(name)
                continue

            self.set_the_package_line(package)
            print(f"\n{self.package_line}:")

            if not tree:
                print(f"  {self.grey}No dependencies.{self.endc}")
            else:
                for line in render_tree(
                    tree,
                    unicode=self.unicode,
                    grey=self.grey,
                    endc=self.endc,
                    color_fn=color_fn,
                    label_fn=label_fn,
                ):
                    print(f"  {line}")

                n_direct = len(tree)
                n_transitive = len(collect_transitive_names(tree))
                parts = [f"{n_direct} direct"]
                if n_transitive:
                    parts.append(f"{n_transitive} transitive")
                print(f"\n  {self.grey}{', '.join(parts)}.{self.endc}")

        logger.info("Finished package dependency tracking for all packages.")

    def set_the_package_line(self, package: str) -> None:
        """Set the formatted string for the main package line.

        Args:
            package (str): Package name.
        """
        logger.debug("Setting package line for package: %s", package)
        self.package_line = f"{self.bold}{self.cyan}{package}{self.endc}"
        if self.option_for_pkg_version:
            self.set_package_version(package)
            self.package_line = (
                f"{self.bold}{self.cyan}{package}{self.endc} {self.yellow}{self.package_version}{self.endc}"
            )
            logger.debug("Package line with version: %s", self.package_line)
        else:
            logger.debug("Package line without version: %s", self.package_line)

    def set_the_package_require_line(self, require: str) -> None:
        """Set the formatted string for a package requirement.

        Args:
            require (str): Requirement name.
        """
        logger.debug("Setting requirement line for: %s", require)
        color: str = ""
        if require not in self.data:
            color = self.red
            logger.debug("Requirement '%s' not found in data, marking with red color.", require)

        self.require_line = f"{color}{require}{self.endc}"

        if self.option_for_pkg_version:
            self.set_package_dependency_version(require)
            self.require_line = f"{color}{require:<{self.require_length}}{self.endc}{self.package_dependency_version}"
            logger.debug("Requirement line with dependency version: %s", self.require_line)
        else:
            logger.debug("Requirement line without dependency version: %s", self.require_line)

    def set_package_dependency_version(self, require: str) -> None:
        """Set the version string for a package dependency.

        Args:
            require (str): Dependency name.
        """
        logger.debug("Setting dependency version for requirement: %s", require)
        self.package_dependency_version = f"{'':>1}(not included)"
        if self.data.get(require):
            self.package_dependency_version = f"{'':>1}{self.yellow}{self.data[require]['version']}{self.endc}"
            logger.debug("Dependency '%s' version found: %s", require, self.package_dependency_version)
        else:
            logger.debug("Dependency '%s' version not found in data. Set to '(not included)'.", require)

    def set_package_version(self, package: str) -> None:
        """Set the main package version.

        Args:
            package (str): Package name.
        """
        logger.debug("Setting main package version for: %s", package)
        self.package_version = self.data.get(package, {}).get("version", "")
        logger.debug("Main package '%s' version set to: %s", package, self.package_version)

    def _build_dep_tree(
        self,
        package: str,
        visited: Optional[frozenset[str]] = None,
    ) -> dict[str, Any]:
        """Recursively build the dependency tree as a nested dict.

        Each key is a direct dependency of its parent; the value is that dep's
        own subtree.  Cycle detection via frozenset visited prevents infinite
        recursion.  Packages not in self.data appear as leaf nodes (empty dict)
        and are coloured red by the caller.

        Args:
            package: The package whose dependencies to expand.
            visited: Already-visited packages — prevents cycles.

        Returns:
            dict[str, Any]: Nested dict {dep: {sub_dep: {...}}, ...}.
        """
        if visited is None:
            visited = frozenset()
        visited = visited | {package}

        raw: list[str] = cast(list[str], self.data.get(package, {}).get("requires", []))
        direct = sorted(
            r for r in raw if r and r != "%README%" and r not in visited and (r in self.data or self.view_missing_deps)
        )
        return {dep: self._build_dep_tree(dep, visited) for dep in direct}
