#!/usr/bin/python3


from __future__ import annotations

import argparse
import difflib
import logging
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Callable

import tomlkit
from tomlkit import items

# This script always runs as root.
log_file: Path = Path("/var/log/slpkg/new_configs.log")
slpkg_toml_path: Path = Path("/etc/slpkg/slpkg.toml")

log_file.parent.mkdir(parents=True, exist_ok=True)

configured_logging_level = logging.INFO

try:
    if slpkg_toml_path.exists():
        config_data = tomlkit.parse(slpkg_toml_path.read_text(encoding="utf-8"))
        for section_data in config_data.values():
            if isinstance(section_data, items.Table) and "LOGGING_LEVEL" in section_data:
                level_str = str(section_data["LOGGING_LEVEL"]).upper()
                level_int = getattr(logging, level_str, None)
                if isinstance(level_int, int):
                    configured_logging_level = level_int
                break
except Exception as e:  # pylint: disable=[W0718]
    print(f"Error parsing {slpkg_toml_path} for logging level: {e}. Using default INFO for logging.", file=sys.stderr)
    configured_logging_level = logging.INFO

logging.basicConfig(
    filename=log_file,
    level=configured_logging_level,
    format="%(levelname)s: %(asctime)s - %(name)s - %(funcName)s - %(message)s",
    filemode="a",
)

logger = logging.getLogger(__name__)

logger.info("new_configs.py script started. Logging initialized.")
logger.debug("Log file path: %s", log_file)
logger.info("Logging level set to: %s (from %s)", logging.getLevelName(configured_logging_level), slpkg_toml_path)


class NewConfigs:  # pylint: disable=[R0902,R0904]
    """Tool that manage the config files."""

    def __init__(self, no_colors: bool = False) -> None:
        logger.debug("Initializing NewConfigs instance.")
        self.etc_path: Path = Path("/etc/slpkg")
        self.slpkg_config: Path = self.etc_path / "slpkg.toml"
        self.repositories_config: Path = self.etc_path / "repositories.toml"
        self.blacklist_config: Path = self.etc_path / "blacklist.toml"
        self.slpkg_config_new: Path = self.etc_path / "slpkg.toml.new"
        self.repositories_config_new: Path = self.etc_path / "repositories.toml.new"
        self.blacklist_config_new: Path = self.etc_path / "blacklist.toml.new"

        self.bold: str = "\033[1m"
        self.red: str = "\x1b[91m"
        self.green: str = "\x1b[32m"
        self.bgreen: str = f"{self.bold}{self.green}"
        self.yellow: str = "\x1b[93m"
        self.byellow: str = f"{self.bold}{self.yellow}"
        self.endc: str = "\x1b[0m"

        if no_colors:
            self.set_no_colors()
            logger.info("Colors disabled via --no-colors argument.")

        logger.debug("NewConfigs initialization complete.")

    def set_no_colors(self) -> None:
        """Switch off colors."""
        logger.debug("Setting no colors.")
        self.bold = ""
        self.red = ""
        self.green = ""
        self.bgreen = ""
        self.yellow = ""
        self.byellow = ""
        self.endc = ""

    def check(self) -> None:
        """Check for .new files."""
        logger.info("Checking for NEW configuration files.")
        print("Checking for NEW configuration files...\n")

        has_new_files = (
            self.slpkg_config_new.is_file()
            or self.blacklist_config_new.is_file()
            or self.repositories_config_new.is_file()
        )

        if has_new_files:
            logger.info("New configuration files found.")
            print("There are NEW files:\n")

            if self.slpkg_config_new.is_file():
                logger.info("Found: %s", self.slpkg_config_new)
                print(f"{self.bgreen:>12}{self.slpkg_config_new}{self.endc}")

            if self.repositories_config_new.is_file():
                logger.info("Found: %s", self.repositories_config_new)
                print(f"{self.bgreen:>12}{self.repositories_config_new}{self.endc}")

            if self.blacklist_config_new.is_file():
                logger.info("Found: %s", self.blacklist_config_new)
                print(f"{self.bgreen:>12}{self.blacklist_config_new}{self.endc}")

            print(
                f"\nWhat would you like to do ({self.byellow}K{self.endc}/{self.byellow}O{self.endc}/"
                f"{self.byellow}R{self.endc}/{self.byellow}P{self.endc}/{self.byellow}M{self.endc})?\n"
            )

            print(
                f"{'':>2}({self.byellow}K{self.endc})eep the old files and consider '.new' files later.\n"
                f"{'':>2}({self.byellow}O{self.endc})verwrite all old files with the new ones.\n"
                f"{'':>5}The old files will be stored with the suffix '.orig'.\n"
                f"{'':>2}({self.byellow}R{self.endc})emove all '.new' files.\n"
                f"{'':>2}({self.byellow}P{self.endc})rompt K, O, R, D, V, M selection for every single file.\n"
                f"{'':>2}({self.byellow}M{self.endc})erge new keys into existing files, preserving your settings.\n"
                f"{'':>5}Only keys absent from your current config are added.\n"
            )

            self.menu()

        else:
            logger.info("No .new files found.")
            print(f"\n{'No .new files found.':>23}\n")

    def menu(self) -> None:
        """Menu of choices."""
        choice: str = input("Choice: ").lower()
        logger.debug("User choice: '%s'", choice)

        arguments: dict[str, Callable[..., None]] = {
            "k": self.keep,
            "o": self.overwrite,
            "r": self.remove,
            "p": self.prompt,
            "m": self.merge,
        }

        try:
            arguments[choice]()
        except KeyError:
            logger.warning("Invalid choice '%s'. Defaulting to 'Keep'.", choice)
            self.keep()

    @staticmethod
    def keep() -> None:
        """Print a message."""
        logger.info("User chose to keep existing files. No changes made.")
        print("\nNo changes were made.\n")

    def overwrite(self) -> None:
        """Copy the .new files and rename the olds to .orig."""
        logger.info("Starting overwrite process for new config files.")

        if self.slpkg_config_new.is_file():
            self.overwrite_config_file()

        if self.repositories_config_new.is_file():
            self.overwrite_repositories_file()

        if self.blacklist_config_new.is_file():
            self.overwrite_blacklist_file()

        print()

    def overwrite_config_file(self) -> None:
        """Copy the slpkg.toml.new file and rename the old to .orig."""
        logger.debug("Overwriting slpkg.toml with slpkg.toml.new.")
        try:
            if self.slpkg_config.is_file():
                shutil.copy(self.slpkg_config, f"{self.slpkg_config}.orig")
                logger.info("Backed up %s to %s.orig", self.slpkg_config, self.slpkg_config)
                print(f"\ncp {self.green}{self.slpkg_config}{self.endc} -> {self.slpkg_config}.orig")

            shutil.move(self.slpkg_config_new, self.slpkg_config)
            logger.info("Moved %s to %s", self.slpkg_config_new, self.slpkg_config)
            print(f"mv {self.slpkg_config_new} -> {self.green}{self.slpkg_config}{self.endc}")
        except Exception as e:  # pylint: disable=[W0718]
            logger.error("Failed to overwrite %s: %s", self.slpkg_config, e, exc_info=True)
            print(f"{self.red}Error overwriting {self.slpkg_config}: {e}{self.endc}")

    def overwrite_repositories_file(self) -> None:
        """Copy the repositories.toml.new file and rename the old to .orig."""
        logger.debug("Overwriting repositories.toml with repositories.toml.new.")
        try:
            if self.repositories_config.is_file():
                shutil.copy(self.repositories_config, f"{self.repositories_config}.orig")
                logger.info("Backed up %s to %s.orig", self.repositories_config, self.repositories_config)
                print(f"\ncp {self.green}{self.repositories_config}{self.endc} -> {self.repositories_config}.orig")

            shutil.move(self.repositories_config_new, self.repositories_config)
            logger.info("Moved %s to %s", self.repositories_config_new, self.repositories_config)
            print(f"mv {self.repositories_config_new} -> {self.green}{self.repositories_config}{self.endc}")
        except Exception as e:  # pylint: disable=[W0718]
            logger.error("Failed to overwrite %s: %s", self.repositories_config, e, exc_info=True)
            print(f"{self.red}Error overwriting {self.repositories_config}: {e}{self.endc}")

    def overwrite_blacklist_file(self) -> None:
        """Copy the blacklist.toml.new file and rename the old to .orig."""
        logger.debug("Overwriting blacklist.toml with blacklist.toml.new.")
        try:
            if self.blacklist_config.is_file():
                shutil.copy(self.blacklist_config, f"{self.blacklist_config}.orig")
                logger.info("Backed up %s to %s.orig", self.blacklist_config, self.blacklist_config)
                print(f"\ncp {self.green}{self.blacklist_config}{self.endc} -> {self.blacklist_config}.orig")

            shutil.move(self.blacklist_config_new, self.blacklist_config)
            logger.info("Moved %s to %s", self.blacklist_config_new, self.blacklist_config)
            print(f"mv {self.blacklist_config_new} -> {self.green}{self.blacklist_config}{self.endc}")
        except Exception as e:  # pylint: disable=[W0718]
            logger.error("Failed to overwrite %s: %s", self.blacklist_config, e, exc_info=True)
            print(f"{self.red}Error overwriting {self.blacklist_config}: {e}{self.endc}")

    @staticmethod
    def _find_additions(
        current: tomlkit.TOMLDocument, new_doc: tomlkit.TOMLDocument
    ) -> list[str]:
        """Return names of keys/sections in new_doc that are absent from current."""
        additions: list[str] = []
        for section_name, sec_data in new_doc.items():
            if isinstance(sec_data, items.Table):
                if section_name not in current:
                    additions.append(f"[{section_name}]")
                else:
                    additions.extend(
                        f"{section_name}.{key}"
                        for key in sec_data
                        if key not in current[section_name]  # type: ignore[operator]
                    )
            elif section_name not in current:
                additions.append(section_name)
        return additions

    @staticmethod
    def _find_removals(
        current: tomlkit.TOMLDocument, new_doc: tomlkit.TOMLDocument
    ) -> list[str]:
        """Return names of keys/sections in current that are absent from new_doc."""
        removals: list[str] = []
        for section_name, sec_data in current.items():
            if isinstance(sec_data, items.Table):
                if section_name not in new_doc:
                    removals.append(f"[{section_name}]")
                else:
                    new_section = new_doc[section_name]
                    if isinstance(new_section, items.Table):
                        removals.extend(
                            f"{section_name}.{key}"
                            for key in sec_data
                            if key not in new_section
                        )
            elif section_name not in new_doc:
                removals.append(section_name)
        return removals

    def _compute_merge_diff(
        self, current_file: Path, new_file: Path
    ) -> tuple[list[str], list[str]]:
        """Compute additions and removals between current and new config (dry-run, no writes).

        Args:
            current_file (Path): The user's existing config file.
            new_file (Path): The incoming '.new' config file.

        Returns:
            tuple[list[str], list[str]]: (additions, removals) key/section names.
        """
        current = tomlkit.parse(current_file.read_text(encoding="utf-8"))
        new_doc = tomlkit.parse(new_file.read_text(encoding="utf-8"))
        return self._find_additions(current, new_doc), self._find_removals(current, new_doc)

    def _confirm_merge(self, current_file: Path, additions: list[str], removals: list[str]) -> bool:
        """Show pending merge changes and ask for user confirmation.

        Args:
            current_file (Path): The existing config file path (for display).
            additions (list[str]): Keys/sections that would be added.
            removals (list[str]): Keys/sections that would be removed.

        Returns:
            bool: True if the user confirms, False otherwise.
        """
        n_total = len(additions) + len(removals)
        print(f"\nPending changes for {self.green}{current_file}{self.endc} ({n_total} change(s)):")
        for key in additions:
            print(f"{'':>4}{self.green}+{self.endc} {key}")
        for key in removals:
            print(f"{'':>4}{self.red}-{self.endc} {key}")
        print()

        confirm = input(f"Apply merge changes? ({self.byellow}Y{self.endc}/{self.byellow}N{self.endc}): ").strip().lower()
        logger.debug("User confirmation for merge of %s: '%s'", current_file, confirm)
        return confirm == "y"

    @staticmethod
    def _apply_removals(
        current: tomlkit.TOMLDocument, new_doc: tomlkit.TOMLDocument
    ) -> list[str]:
        """Remove from current any keys/sections absent in new_doc. Returns removed names."""
        removals: list[str] = []
        for section_name in list(current.keys()):
            sec_data = current[section_name]
            if isinstance(sec_data, items.Table):
                if section_name not in new_doc:
                    del current[section_name]
                    removals.append(f"[{section_name}]")
                    logger.debug("Removed obsolete section: [%s]", section_name)
                else:
                    new_section = new_doc[section_name]
                    if isinstance(new_section, items.Table):
                        for key in [k for k in sec_data if k not in new_section]:
                            del current[section_name][key]  # type: ignore[union-attr]
                            removals.append(f"{section_name}.{key}")
                            logger.debug("Removed obsolete key: %s.%s", section_name, key)
            elif section_name not in new_doc:
                del current[section_name]
                removals.append(section_name)
                logger.debug("Removed obsolete top-level key: %s", section_name)
        return removals

    @staticmethod
    def _apply_additions(
        current: tomlkit.TOMLDocument, new_doc: tomlkit.TOMLDocument
    ) -> list[str]:
        """Add to current any keys/sections present in new_doc but absent. Returns added names."""
        additions: list[str] = []
        for section_name, sec_data in new_doc.items():
            if isinstance(sec_data, items.Table):
                if section_name not in current:
                    current.add(section_name, sec_data)
                    additions.append(f"[{section_name}]")
                    logger.debug("Added new section: [%s]", section_name)
                else:
                    for key, value in sec_data.items():
                        if key not in current[section_name]:  # type: ignore[operator]
                            current[section_name].add(key, value)  # type: ignore[union-attr]
                            additions.append(f"{section_name}.{key}")
                            logger.debug("Added new key: %s.%s", section_name, key)
            elif section_name not in current:
                current.add(section_name, sec_data)
                additions.append(section_name)
                logger.debug("Added new top-level key: %s", section_name)
        return additions

    def _merge_toml(self, current_file: Path, new_file: Path) -> tuple[list[str], list[str]]:
        """Sync current_file with new_file: add new keys, remove obsolete ones, preserve user values.

        The current file is backed up to '.orig' before writing if any changes are made.

        Args:
            current_file (Path): The user's existing config file.
            new_file (Path): The incoming '.new' config file.

        Returns:
            tuple[list[str], list[str]]: (additions, removals) key/section names.
        """
        logger.info("Merging %s into %s", new_file, current_file)
        current = tomlkit.parse(current_file.read_text(encoding="utf-8"))
        new_doc = tomlkit.parse(new_file.read_text(encoding="utf-8"))

        removals = self._apply_removals(current, new_doc)
        additions = self._apply_additions(current, new_doc)

        if additions or removals:
            shutil.copy(current_file, f"{current_file}.orig")
            logger.info("Backed up %s to %s.orig", current_file, current_file)
            current_file.write_text(tomlkit.dumps(current), encoding="utf-8")
            logger.info("Merge complete. Added: %s, Removed: %s", additions, removals)

        return additions, removals

    def merge(self) -> None:
        """Merge new keys from all .new files into the existing config files."""
        logger.info("Starting merge process for all new config files.")
        print()

        if self.slpkg_config_new.is_file():
            self.merge_config_file()
        else:
            print(f"\n{self.slpkg_config} is already up-to-date. No changes needed.")

        if self.repositories_config_new.is_file():
            self.merge_repositories_file()
        else:
            print(f"\n{self.repositories_config} is already up-to-date. No changes needed.")

        if self.blacklist_config_new.is_file():
            self.merge_blacklist_file()
        else:
            print(f"\n{self.blacklist_config} is already up-to-date. No changes needed.")

        print()

    def merge_config_file(self) -> None:
        """Merge new keys from slpkg.toml.new into slpkg.toml."""
        logger.debug("Merging %s into %s", self.slpkg_config_new, self.slpkg_config)
        try:
            if not self.slpkg_config.is_file():
                shutil.move(self.slpkg_config_new, self.slpkg_config)
                print(f"mv {self.slpkg_config_new} -> {self.green}{self.slpkg_config}{self.endc}")
                return

            additions, removals = self._compute_merge_diff(self.slpkg_config, self.slpkg_config_new)
            if not additions and not removals:
                print(f"\n{self.slpkg_config} is already up-to-date. No changes needed.")
                self.slpkg_config_new.unlink(missing_ok=True)
                logger.info("Removed %s after merge (nothing to change).", self.slpkg_config_new)
                return

            if not self._confirm_merge(self.slpkg_config, additions, removals):
                logger.info("User cancelled merge for %s.", self.slpkg_config)
                print("Merge cancelled. No changes made.")
                return

            added, removed = self._merge_toml(self.slpkg_config, self.slpkg_config_new)
            n = len(added) + len(removed)
            print(f"\nMerged into {self.green}{self.slpkg_config}{self.endc} ({n} change(s)):")
            for key in added:
                print(f"{'':>4}{self.green}+{self.endc} {key}")
            for key in removed:
                print(f"{'':>4}{self.red}-{self.endc} {key}")
            print(f"\ncp {self.green}{self.slpkg_config}{self.endc} -> {self.slpkg_config}.orig")

            shutil.move(self.slpkg_config_new, f"{self.slpkg_config_new}.orig")
            logger.info("Renamed %s to %s.orig after merge.", self.slpkg_config_new, self.slpkg_config_new)
            print(f"mv {self.slpkg_config_new} -> {self.slpkg_config_new}.orig")
        except Exception as e:  # pylint: disable=[W0718]
            logger.error("Failed to merge %s: %s", self.slpkg_config, e, exc_info=True)
            print(f"{self.red}Error merging {self.slpkg_config}: {e}{self.endc}")

    def merge_repositories_file(self) -> None:
        """Merge new keys from repositories.toml.new into repositories.toml."""
        logger.debug("Merging %s into %s", self.repositories_config_new, self.repositories_config)
        try:
            if not self.repositories_config.is_file():
                shutil.move(self.repositories_config_new, self.repositories_config)
                print(f"mv {self.repositories_config_new} -> {self.green}{self.repositories_config}{self.endc}")
                return

            additions, removals = self._compute_merge_diff(self.repositories_config, self.repositories_config_new)
            if not additions and not removals:
                print(f"\n{self.repositories_config} is already up-to-date. No changes needed.")
                self.repositories_config_new.unlink(missing_ok=True)
                logger.info("Removed %s after merge (nothing to change).", self.repositories_config_new)
                return

            if not self._confirm_merge(self.repositories_config, additions, removals):
                logger.info("User cancelled merge for %s.", self.repositories_config)
                print("Merge cancelled. No changes made.")
                return

            added, removed = self._merge_toml(self.repositories_config, self.repositories_config_new)
            n = len(added) + len(removed)
            print(f"\nMerged into {self.green}{self.repositories_config}{self.endc} ({n} change(s)):")
            for key in added:
                print(f"{'':>4}{self.green}+{self.endc} {key}")
            for key in removed:
                print(f"{'':>4}{self.red}-{self.endc} {key}")
            print(f"\ncp {self.green}{self.repositories_config}{self.endc} -> {self.repositories_config}.orig")

            shutil.move(self.repositories_config_new, f"{self.repositories_config_new}.orig")
            logger.info("Renamed %s to %s.orig after merge.", self.repositories_config_new, self.repositories_config_new)
            print(f"mv {self.repositories_config_new} -> {self.repositories_config_new}.orig")
        except Exception as e:  # pylint: disable=[W0718]
            logger.error("Failed to merge %s: %s", self.repositories_config, e, exc_info=True)
            print(f"{self.red}Error merging {self.repositories_config}: {e}{self.endc}")

    def merge_blacklist_file(self) -> None:
        """Merge new keys from blacklist.toml.new into blacklist.toml."""
        logger.debug("Merging %s into %s", self.blacklist_config_new, self.blacklist_config)
        try:
            if not self.blacklist_config.is_file():
                shutil.move(self.blacklist_config_new, self.blacklist_config)
                print(f"mv {self.blacklist_config_new} -> {self.green}{self.blacklist_config}{self.endc}")
                return

            additions, removals = self._compute_merge_diff(self.blacklist_config, self.blacklist_config_new)
            if not additions and not removals:
                print(f"\n{self.blacklist_config} is already up-to-date. No changes needed.")
                self.blacklist_config_new.unlink(missing_ok=True)
                logger.info("Removed %s after merge (nothing to change).", self.blacklist_config_new)
                return

            if not self._confirm_merge(self.blacklist_config, additions, removals):
                logger.info("User cancelled merge for %s.", self.blacklist_config)
                print("Merge cancelled. No changes made.")
                return

            added, removed = self._merge_toml(self.blacklist_config, self.blacklist_config_new)
            n = len(added) + len(removed)
            print(f"\nMerged into {self.green}{self.blacklist_config}{self.endc} ({n} change(s)):")
            for key in added:
                print(f"{'':>4}{self.green}+{self.endc} {key}")
            for key in removed:
                print(f"{'':>4}{self.red}-{self.endc} {key}")
            print(f"\ncp {self.green}{self.blacklist_config}{self.endc} -> {self.blacklist_config}.orig")

            shutil.move(self.blacklist_config_new, f"{self.blacklist_config_new}.orig")
            logger.info("Renamed %s to %s.orig after merge.", self.blacklist_config_new, self.blacklist_config_new)
            print(f"mv {self.blacklist_config_new} -> {self.blacklist_config_new}.orig")
        except Exception as e:  # pylint: disable=[W0718]
            logger.error("Failed to merge %s: %s", self.blacklist_config, e, exc_info=True)
            print(f"{self.red}Error merging {self.blacklist_config}: {e}{self.endc}")

    def remove(self) -> None:
        """Remove the .new files."""
        logger.info("Starting removal process for new config files.")
        print()
        self.remove_config_new_file()
        self.remove_repositories_new_file()
        self.remove_blacklist_new_file()
        print()

    def remove_config_new_file(self) -> None:
        """Remove slpkg.toml.new file."""
        logger.debug("Attempting to remove %s", self.slpkg_config_new)
        try:
            if self.slpkg_config_new.is_file():
                self.slpkg_config_new.unlink()
                logger.info("Removed file: %s", self.slpkg_config_new)
                print(f"rm {self.red}{self.slpkg_config_new}{self.endc}")
        except OSError as e:
            logger.error("Failed to remove %s: %s", self.slpkg_config_new, e, exc_info=True)
            print(f"{self.red}Error removing {self.slpkg_config_new}: {e}{self.endc}")

    def remove_repositories_new_file(self) -> None:
        """Remove repositories.toml.new file."""
        logger.debug("Attempting to remove %s", self.repositories_config_new)
        try:
            if self.repositories_config_new.is_file():
                self.repositories_config_new.unlink()
                logger.info("Removed file: %s", self.repositories_config_new)
                print(f"rm {self.red}{self.repositories_config_new}{self.endc}")
        except OSError as e:
            logger.error("Failed to remove %s: %s", self.repositories_config_new, e, exc_info=True)
            print(f"{self.red}Error removing {self.repositories_config_new}: {e}{self.endc}")

    def remove_blacklist_new_file(self) -> None:
        """Remove blacklist.toml.new file."""
        logger.debug("Attempting to remove %s", self.blacklist_config_new)
        try:
            if self.blacklist_config_new.is_file():
                self.blacklist_config_new.unlink()
                logger.info("Removed file: %s", self.blacklist_config_new)
                print(f"rm {self.red}{self.blacklist_config_new}{self.endc}")
        except OSError as e:
            logger.error("Failed to remove %s: %s", self.blacklist_config_new, e, exc_info=True)
            print(f"{self.red}Error removing {self.blacklist_config_new}: {e}{self.endc}")

    def prompt(self) -> None:
        """Prompt K, O, R selection for every single file."""
        logger.info("Starting interactive prompt for new config files.")
        print(
            f"\n{'':>2}({self.byellow}K{self.endc})eep, ({self.byellow}O{self.endc})verwrite, "
            f"({self.byellow}R{self.endc})emove, ({self.byellow}D{self.endc})iff, "
            f"({self.byellow}V{self.endc})imdiff, ({self.byellow}M{self.endc})erge\n"
        )

        if self.slpkg_config_new.is_file():
            self.prompt_slpkg_config()

        if self.repositories_config_new.is_file():
            self.prompt_repositories_config()

        if self.blacklist_config_new.is_file():
            self.prompt_blacklist_config()

    def prompt_slpkg_config(self) -> None:
        """Prompt for slpkg.toml file."""
        logger.debug("Prompting for %s", self.slpkg_config_new)
        make: str = input(
            f"{self.bgreen}{self.slpkg_config_new}{self.endc} - "
            f"({self.byellow}K{self.endc}/{self.byellow}O{self.endc}/"
            f"{self.byellow}R{self.endc}/{self.byellow}D{self.endc}/"
            f"{self.byellow}V{self.endc}): "
        ).lower()
        logger.debug("User action for %s: %s", self.slpkg_config_new, make)

        if make == "k":
            logger.info("User chose to keep %s", self.slpkg_config_new)
        elif make == "o":
            self.overwrite_config_file()
            print()
        elif make == "r":
            print()
            self.remove_config_new_file()
            print()
        elif make == "d":
            self.diff_files(self.slpkg_config_new, self.slpkg_config)
            self.prompt_slpkg_config()
        elif make == "v":
            self.vimdiff(self.slpkg_config_new, self.slpkg_config)
            self.prompt_slpkg_config()
        elif make == "m":
            self.merge_config_file()
            print()
        else:
            logger.warning("Invalid input for %s: '%s'. Keeping file by default.", self.slpkg_config_new, make)

    def prompt_repositories_config(self) -> None:
        """Prompt for repositories.toml file."""
        logger.debug("Prompting for %s", self.repositories_config_new)
        make: str = input(
            f"{self.bgreen}{self.repositories_config_new}{self.endc} - "
            f"({self.byellow}K{self.endc}/{self.byellow}O{self.endc}/"
            f"{self.byellow}R{self.endc}/{self.byellow}D{self.endc}/"
            f"{self.byellow}V{self.endc}): "
        ).lower()
        logger.debug("User action for %s: %s", self.repositories_config_new, make)

        if make == "k":
            logger.info("User chose to keep %s", self.repositories_config_new)
        elif make == "o":
            self.overwrite_repositories_file()
            print()
        elif make == "r":
            print()
            self.remove_repositories_new_file()
            print()
        elif make == "d":
            self.diff_files(self.repositories_config_new, self.repositories_config)
            self.prompt_repositories_config()
        elif make == "v":
            self.vimdiff(self.repositories_config_new, self.repositories_config)
            self.prompt_repositories_config()
        elif make == "m":
            self.merge_repositories_file()
            print()
        else:
            logger.warning("Invalid input for %s: '%s'. Keeping file by default.", self.repositories_config_new, make)

    def prompt_blacklist_config(self) -> None:
        """Prompt for blacklist.toml file."""
        logger.debug("Prompting for %s", self.blacklist_config_new)
        make: str = input(
            f"{self.bgreen}{self.blacklist_config_new}{self.endc} - "
            f"({self.byellow}K{self.endc}/{self.byellow}O{self.endc}/"
            f"{self.byellow}R{self.endc}/{self.byellow}D{self.endc}/"
            f"{self.byellow}V{self.endc}): "
        ).lower()
        logger.debug("User action for %s: %s", self.blacklist_config_new, make)

        if make == "k":
            logger.info("User chose to keep %s", self.blacklist_config_new)
        elif make == "o":
            self.overwrite_blacklist_file()
            print()
        elif make == "r":
            print()
            self.remove_blacklist_new_file()
            print()
        elif make == "d":
            self.diff_files(self.blacklist_config_new, self.blacklist_config)
            self.prompt_blacklist_config()
        elif make == "v":
            self.vimdiff(self.blacklist_config_new, self.blacklist_config)
            self.prompt_blacklist_config()
        elif make == "m":
            self.merge_blacklist_file()
            print()
        else:
            logger.warning("Invalid input for %s: '%s'. Keeping file by default.", self.blacklist_config_new, make)

    @staticmethod
    def diff_files(new_file: Path, current_file: Path) -> None:
        """Diff the .new and the current file."""
        logger.info("Performing diff between %s and %s", current_file, new_file)
        try:
            diff = difflib.context_diff(
                current_file.read_text(encoding="utf-8").splitlines(keepends=True),
                new_file.read_text(encoding="utf-8").splitlines(keepends=True),
                fromfile=str(current_file),
                tofile=str(new_file),
            )
            for line in diff:
                print(line, end="")
            logger.info("Diff completed successfully.")
        except FileNotFoundError as e:
            logger.error(
                "One of the files not found for diffing: %s (Files: %s, %s)", e, current_file, new_file, exc_info=True
            )
            print(f"Error: One of the files not found for diffing: {e}")

    @staticmethod
    def vimdiff(file1: Path, file2: Path) -> None:
        """Show vimdiff command.

        Args:
            file1 (Path): First file.
            file2 (Path): Second file.

        Raises:
            SystemExit: Raise exit code.
        """
        logger.info("Attempting to open vimdiff for %s and %s", file1, file2)
        try:
            subprocess.run(["vimdiff", str(file1), str(file2)], check=True)
            logger.info("vimdiff command executed successfully.")
        except subprocess.CalledProcessError as e:
            logger.error("vimdiff command failed with exit code %d: %s", e.returncode, e, exc_info=True)
            print(f"Error running vimdiff: Command failed with exit code {e.returncode}")
            raise SystemExit(e.returncode) from e
        except FileNotFoundError as e:
            logger.error("vimdiff command not found. Please ensure vim is installed and in your PATH.", exc_info=True)
            print("Error: 'vimdiff' command not found. Please ensure vim is installed and in your PATH.")
            raise SystemExit(1) from e
        except Exception as e:
            logger.critical("An unexpected error occurred while running vimdiff: %s", e, exc_info=True)
            print(f"An unexpected error occurred: {e}")
            raise SystemExit(1) from e


def main() -> None:
    """Manage arguments."""
    parser = argparse.ArgumentParser(
        description="Tool to manage slpkg configuration files (.new files).",
        formatter_class=argparse.RawTextHelpFormatter,
    )
    parser.add_argument("--no-colors", action="store_true", help="Disable the output colors")

    args = parser.parse_args()

    try:
        config = NewConfigs(no_colors=args.no_colors)
        config.check()
    except (KeyboardInterrupt, EOFError):
        logger.info("Operation cancelled by user (KeyboardInterrupt/EOFError).")
        print("\nOperation cancelled by user.")
        sys.exit(1)
    except Exception as e:  # pylint: disable=[W0718]
        logger.critical("An unexpected error occurred in main execution: %s", e, exc_info=True)
        print(f"An unexpected error occurred: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()
