#!/usr/bin/python3


from __future__ import annotations

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

from slpkg.config import config_load
from slpkg.utilities import Utilities

logger = logging.getLogger(__name__)


class Orphans:  # pylint: disable=[R0902]
    """Find and display installed packages no longer needed as dependencies."""

    def __init__(self, options: Optional[dict[str, Any]] = None) -> None:
        self.deps_log_file: Path = config_load.deps_log_file
        self.marks_log_file: Path = config_load.marks_log_file
        self.bold = config_load.bold
        self.green = config_load.green
        self.grey = config_load.grey
        self.cyan = config_load.cyan
        self.endc = config_load.endc
        self.quiet: bool = (options or {}).get("option_quiet", False)
        self.utils = Utilities()

    def _read_marks_log(self) -> set[str]:
        """Read marks.log, returning an empty set if missing or invalid."""
        if not self.marks_log_file.is_file():
            return set()
        try:
            data = json.loads(self.marks_log_file.read_text(encoding="utf-8"))
            if not isinstance(data, list):
                logger.warning("marks.log is not a JSON array: %s", self.marks_log_file)
                return set()
            return {str(p) for p in data}
        except (json.JSONDecodeError, OSError) as e:
            logger.warning("Failed to read marks.log '%s': %s", self.marks_log_file, e)
            return set()

    def _read_deps_log(self) -> dict[str, list[str]]:
        """Read deps.log, returning an empty dict if missing or invalid."""
        if not self.deps_log_file.is_file():
            logger.info("deps.log not found: %s", self.deps_log_file)
            return {}
        try:
            data = json.loads(self.deps_log_file.read_text(encoding="utf-8"))
            if not isinstance(data, dict):
                logger.warning("deps.log is not a JSON object: %s", self.deps_log_file)
                return {}
            return data
        except (json.JSONDecodeError, OSError) as e:
            logger.error("Failed to read deps.log '%s': %s", self.deps_log_file, e)
            return {}

    def find(self) -> list[str]:
        """Return a sorted list of orphaned package names.

        Orphans are packages that were installed as dependencies but are no
        longer required by any currently installed top-level package.

        Returns:
            list[str]: Sorted list of orphaned package names.
        """
        deps_log = self._read_deps_log()
        if not deps_log:
            return []

        installed = set(self.utils.all_installed().keys())

        # All deps still needed by currently installed top-level packages.
        needed: set[str] = set()
        for key, deps in deps_log.items():
            if key in installed:
                needed.update(deps)

        # Candidates = packages that appear in any dep list AND are installed.
        all_tracked_deps: set[str] = {dep for deps in deps_log.values() for dep in deps}
        candidates = all_tracked_deps & installed

        # Explicit = top-level installs still installed OR manually marked packages.
        marked = self._read_marks_log()
        explicit = (set(deps_log.keys()) & installed) | (marked & installed)

        orphans = candidates - needed - explicit
        logger.info("Found %d orphaned packages.", len(orphans))
        return sorted(orphans)

    def view(self) -> None:
        """Print a formatted list of orphaned packages."""
        orphans = self.find()

        if self.quiet:
            for name in orphans:
                print(name)
            logger.info("Orphans view completed (quiet). %d orphans displayed.", len(orphans))
            return

        print(f"{self.bold}Orphaned packages:{self.endc}")

        if not orphans:
            print(" No orphaned packages found.")
            return

        installed = self.utils.all_installed()
        for name in orphans:
            filename = installed.get(name, "")
            suffix = filename[len(name) + 1:] if filename.startswith(f"{name}-") else filename
            print(f"  {self.green}{name:<24}{self.endc}  {self.grey}{suffix}{self.endc}")
        count = len(orphans)
        noun = "package" if count == 1 else "packages"
        print(f"\nFound {self.cyan}{count}{self.endc} orphaned {noun}.")
        print(f"Run '{self.bold}slpkg remove <package>{self.endc}' to remove them.")
        logger.info("Orphans view completed. %d orphans displayed.", count)
