#!/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 Pin:  # pylint: disable=[R0902]
    """Pin packages to prevent them from being upgraded."""

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

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

    def _write_pins(self, pins: set[str]) -> None:
        """Write sorted list of pins to pins.log."""
        try:
            self.pins_log_file.write_text(
                json.dumps(sorted(pins), indent=2) + "\n",
                encoding="utf-8",
            )
            logger.info("pins.log written: %d entries.", len(pins))
        except OSError as e:
            logger.error("Failed to write pins.log '%s': %s", self.pins_log_file, e)

    def view(self) -> None:
        """List pinned packages, or pin/unpin based on self.packages."""
        if self.unpin and not self.packages:
            self._unpin_all()
        elif not self.packages:
            self._list()
        elif self.unpin:
            self._unpin()
        else:
            self._pin()

    def _list(self) -> None:
        """Display all pinned packages."""
        pins = sorted(self.read_pins())
        if self.quiet:
            for name in pins:
                print(name)
            return
        print(f"{self.bold}Pinned packages (excluded from upgrades):{self.endc}")
        if not pins:
            print(" No pinned packages found.")
            return
        for name in pins:
            installed = self.utils.is_package_installed(name)
            label = installed if installed else f"{self.grey}not installed{self.endc}"
            print(f"  {self.green}{name}{self.endc}  {self.grey}{label}{self.endc}")
        count = len(pins)
        noun = "package" if count == 1 else "packages"
        print(f"\nFound {self.cyan}{count}{self.endc} pinned {noun}.")
        print(f"Run '{self.bold}slpkg pin <package> --unpin{self.endc}' to unpin.")
        logger.info("Pin list view completed. %d pins displayed.", count)

    def _pin(self) -> None:
        """Pin packages to prevent them from being upgraded."""
        pins = self.read_pins()
        pins.update(self.packages)
        self._write_pins(pins)
        print(f"{self.bold}Pinned (excluded from upgrades):{self.endc}")
        for name in sorted(self.packages):
            print(f"  {self.green}{name}{self.endc}")
        logger.info("Pinned packages: %s", ", ".join(self.packages))

    def _unpin(self) -> None:
        """Unpin packages (allow upgrades again)."""
        pins = self.read_pins()
        for name in self.packages:
            pins.discard(name)
        self._write_pins(pins)
        print(f"{self.bold}Unpinned (upgrades allowed):{self.endc}")
        for name in self.packages:
            print(f"  {self.grey}{name}{self.endc}")
        logger.info("Unpinned packages: %s", ", ".join(self.packages))

    def _unpin_all(self) -> None:
        """Remove all pins."""
        pins = self.read_pins()
        if not pins:
            print(" No pinned packages found.")
            return
        self._write_pins(set())
        print(f"{self.bold}Unpinned all packages (upgrades allowed):{self.endc}")
        for name in sorted(pins):
            print(f"  {self.grey}{name}{self.endc}")
        logger.info("Unpinned all %d packages.", len(pins))
