#!/usr/bin/python3


import logging
import subprocess
from pathlib import Path
from typing import Any, Optional

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

logger = logging.getLogger(__name__)


class HealthCheck:  # pylint: disable=[R0902,R0903]
    """Checks installed binaries and libraries for missing dependencies using ldd."""

    def __init__(self, options: dict[str, Any], packages: Optional[list[str]] = None) -> None:
        self.options = options
        self.packages = packages or []
        self.utils = Utilities()
        self.view_process = ViewProcess()

        self.cyan = config_load.cyan
        self.green = config_load.green
        self.red = config_load.red
        self.endc = config_load.endc
        self.bold = config_load.bold

    def _is_elf(self, file_path: Path) -> bool:
        """Check if a file is an ELF binary/library."""
        if not file_path.is_file() or file_path.is_symlink():
            return False
        try:
            with file_path.open("rb") as f:
                header = f.read(4)
                return header == b"\x7fELF"
        except (OSError, PermissionError):
            return False

    def _check_ldd(self, file_path: Path) -> list[str]:
        """Run ldd on a file and return a list of missing libraries."""
        try:
            # Run ldd and capture output
            result = subprocess.run(
                ["ldd", str(file_path)],
                capture_output=True,
                text=True,
                check=False,
                env={"LC_ALL": "C"}
            )
            missing = []
            for line in result.stdout.splitlines():
                if "not found" in line:
                    lib_name = line.split("=>")[0].strip()
                    missing.append(lib_name)
            return missing
        except (subprocess.SubprocessError, OSError) as e:
            logger.error("Error running ldd on %s: %s", file_path, e)
            return []

    def _process_package_files(self, pkg_log: Path) -> dict[str, list[str]]:
        """Scan files of a single package for broken links."""
        broken_files: dict[str, list[str]] = {}
        try:
            content = pkg_log.read_text(encoding="utf-8", errors="replace")
            files_section = False
            for line in content.splitlines():
                if line.startswith("FILE LIST:"):
                    files_section = True
                    continue
                if files_section and line.strip():
                    file_path = Path("/") / line.strip()
                    if self._is_target_path(file_path) and self._is_elf(file_path):
                        missing = self._check_ldd(file_path)
                        if missing:
                            broken_files[str(file_path)] = missing
        except (OSError, PermissionError) as e:
            logger.error("Error reading package log %s: %s", pkg_log, e)
        return broken_files

    @staticmethod
    def _is_target_path(file_path: Path) -> bool:
        """Filter for common binary/library directories."""
        str_path = str(file_path)
        return any(p in str_path for p in ["bin/", "lib/", "libexec/"])

    def run(self) -> None:
        """Main execution logic for health-check."""
        self.view_process.message("Scanning system health")

        all_installed = self.utils.all_installed()

        # Determine which packages to scan
        if self.packages:
            # Create a dummy data dict for pattern matching
            dummy_data: dict[str, dict[str, str]] = {name: {} for name in all_installed}
            matched_names = self.utils.apply_package_pattern(dummy_data, self.packages)
            to_scan = {name: all_installed[name] for name in matched_names if name in all_installed}

            if not to_scan:
                self.view_process.done()
                print(f"\n{self.red}Error{self.endc}: No installed packages matched: {', '.join(self.packages)}")
                return
        else:
            to_scan = all_installed

        broken_packages: dict[str, dict[str, Any]] = {}

        for pkg_name, full_name in to_scan.items():
            pkg_log = config_load.log_packages / full_name
            if pkg_log.is_file():
                broken = self._process_package_files(pkg_log)
                if broken:
                    # Extract tag for display
                    tag = self.utils.split_package(full_name).get("tag", "slack")
                    broken_packages[pkg_name] = {"files": broken, "tag": tag}

        self.view_process.done()
        self._display_results(broken_packages)

    def _display_results(self, broken_packages: dict[str, dict[str, Any]]) -> None:
        """Display the scan results to the user."""
        if not broken_packages:
            if not self.options.get("option_quiet"):
                print(f"\nNo broken shared library links found. System health is {self.cyan}excellent{self.endc}.")
            return

        if self.options.get("option_quiet"):
            for pkg in broken_packages:
                print(pkg)
            return

        print(f"\n{self.red}Found {len(broken_packages)} packages with broken shared library links:{self.endc}")

        for pkg, data in broken_packages.items():
            tag_str = f" {self.green}[{data['tag']}]{self.endc}" if data['tag'] else ""
            print(f"\n{self.bold}{pkg}{tag_str}{self.endc}:")
            for binary, missing_libs in data["files"].items():
                print(f"  {binary}")
                for lib in missing_libs:
                    print(f"    {self.red}!!{self.endc} missing: {self.red}{lib}{self.endc}")

        print(f"\nTotal broken packages: {self.red}{len(broken_packages)}{self.endc}")
        print("Consider rebuilding or reinstalling these packages.")
