#!/usr/bin/env python3
"""
gereal - Manage gereal sessions with multiple authentication modes.

This program manages gereal sessions
with commands for attach, start, stop, and status operations.

SPDX-License-Identifier: GPL-2.0-or-later
"""

import argparse
import os
import subprocess
import sys
from abc import ABC, abstractmethod
from typing import Dict, Optional

import yaml


def read_config(path: str) -> Dict[str, Dict[str, str]]:
    """Load and parse configuration file with defaults applied to sessions.

    Reads a YAML configuration file containing 'defaults' and 'sessions' sections.
    Each session in the 'sessions' list is augmented with default values from
    the 'defaults' section for any keys not explicitly defined in the session.
    Sessions are keyed by their 'name' field.

    Args:
        path: Path to the YAML configuration file.

    Returns:
        dict: A dictionary of dictionaries representing sessions, keyed by session
              name, each augmented with default values for missing keys.

    Exits:
        Exits with status 0 if the file is not found or YAML parsing fails.
    """
    if not os.path.isfile(path):
        print(
            f"gereal: config file {path} not found",
            file=sys.stderr,
        )
        sys.exit(0)
    try:
        with open(path, "r", encoding="utf-8") as f:
            config = yaml.safe_load(f)
        defaults = config.get("defaults", {})
        defaults.setdefault("screenrc", "/etc/gereal/screenrc")
        sessions = config.get("sessions", [])
        # Apply defaults to each session and key by name
        augmented_sessions = {}
        for session in sessions:
            # Create a new dict with defaults, then overlay session values
            augmented_session = {**defaults, **session}
            session_name = session.get("name")
            if session_name:
                if "screenrc" not in session:
                    # screenrc is not explicitly set for the session
                    # augmented_session["screenrc" is the default then
                    # if a default session-specific screenrc exists,
                    # overwrite the default
                    per_instance_screenrc = \
                        f"/etc/gereal/screenrc-{session_name}"
                    if os.path.exists(per_instance_screenrc):
                        config["screenrc"] = per_instance_screenrc
                augmented_sessions[session_name] = augmented_session
        return augmented_sessions
    except Exception as e:  # pylint: disable=broad-except
        print(
            f"gereal: failed to parse YAML {path}: {e}",
            file=sys.stderr,
        )
        sys.exit(0)


def can_sudo(command: list, user = "root") -> bool:
    """
    Check if current user is allowed to run a command with sudo.

    Args:
        command: Command to check (as list of arguments)
        user: user to execute command as

    Returns:
        True if sudo is allowed for this command, False otherwise
    """
    return True
    # we cannot determine that in sudo 1.9.16
    # deliberately unreachable code
    current_user = os.getenv("USER", "")
    #self.debug(f"Checking if user {current_user} can sudo: {' '.join(command)}")

    try:
        # sudo -l needs sudo 1.9.17 (trixie has 1.9.16)
        # Use sudo -l to check if the command is allowed
        # -n means non-interactive (fail if password required)
        # -l lists the allowed commands
        result = subprocess.run(
            ["sudo", "-n", "-u", user, "-l"] + command,
            capture_output=True,
            text=True,
            check=False,
        )

        #self.debug(f"sudo -l returncode: {result.returncode}")
        #self.debug(f"sudo -l stdout: {result.stdout}")
        #self.debug(f"sudo -l stderr: {result.stderr}")

        # returncode 0 means the command is allowed
        can_sudo = result.returncode == 0
        #self.debug(f"Can use sudo for this command: {can_sudo}")
        return can_sudo
    except FileNotFoundError:
        #self.debug("sudo command not found")
        return False

class BaseGerealSessionHandler(ABC):
    """Base class for gereal session handlers."""

    def __init__(self, verbose: bool = False):
        """
        Initialize the base attacher.

        Args:
            verbose: Enable verbose debug output
        """
        self.verbose = verbose

    def debug(self, message: str) -> None:
        """
        Print debug message if verbose mode is enabled.

        Args:
            message: Debug message to print
        """
        if self.verbose:
            print(f"[DEBUG] {message}", file=sys.stderr)

    def is_session_running( self, session_name: str) -> bool:
        """
        Check if a session is currently running.

        Args:
            session_name: Name of the session to check
            user: Optional user who owns the session

        Returns:
            True if the session exists and is running, False otherwise
        """
        systemd_service = f"gereal-{session_name}"

        self.debug(f"Checking if systemd {session_name}.service is running")

        cmd = ["systemctl", "--quiet", "is-active", systemd_service]
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            check=False,
        )

        self.debug(f"systemctl is-active returncode: {result.returncode}")
        self.debug(f"systemctl is-active stdout: {result.stdout}")
        self.debug(f"systemctl is-active stderr: {result.stderr}")
        return (result.returncode==0)


    @abstractmethod
    def can_attach(self, session_name: str, config: Dict[str, str]) -> bool:
        """
        Check if this attacher can attach to the session.

        Args:
            session_name: Name of the session to attach to
            config: Configuration for this session

        Returns:
            True if attachment is possible, False otherwise
        """

    @abstractmethod
    def attach(self, session_name: str, config: Dict[str, str]) -> int:
        """
        Attempt to attach to a screen session.

        Args:
            session_name: Name of the session to attach to
            config: Configuration for this session

        Returns:
            Exit code (0 for success, non-zero for failure)
        """

    @abstractmethod
    def get_mode_name(self) -> str:
        """
        Get the name of this attachment mode.

        Returns:
            Mode name as string
        """


class DirectSessionHandler(BaseGerealSessionHandler):
    """Direct attachment to screen sessions."""

    def get_mode_name(self) -> str:
        """Get the name of this attachment mode."""
        return "direct"

    def can_attach(self, session_name: str, config: Dict[str, str]) -> bool:
        """
        Check if direct attachment is possible.

        Args:
            session_name: Name of the session to attach to
            config: Configuration for this session

        Returns:
            Always returns True as direct attach can always be attempted
        """
        self.debug("Direct attach can always be attempted")
        return True

    def attach(self, session_name: str, config: Dict[str, str]) -> int:
        """
        Attempt direct attachment to a screen session.

        Args:
            session_name: Name of the session to attach to
            config: Configuration for this session (unused in direct mode)

        Returns:
            Exit code from the screen command
        """
        session_id = f"gereal:{session_name}"
        print(f"Attempting direct attach to {session_id}")
        self.debug(f"Direct attach mode for session: {session_name}")

        try:
            cmd = ["screen", "-r", session_id]
            self.debug(f"Running command: {' '.join(cmd)}")
            result = subprocess.run(cmd, check=False)
            self.debug(f"Direct attach returncode: {result.returncode}")
            return result.returncode
        except FileNotFoundError:
            self.debug("screen command not found in direct_attach")
            debug("Error: screen command not found", file=sys.stderr)
            return 1


class MultiuserSessionHandler(BaseGerealSessionHandler):
    """Multiuser attachment to screen sessions."""

    def get_mode_name(self) -> str:
        """Get the name of this attachment mode."""
        return "multiuser"

    def check_screenrc_multiuser(self, screenrc_path: str) -> bool:
        """
        Check if a screenrc file contains appropriate multiuser and acl commands.

        Args:
            screenrc_path: Path to the screenrc file

        Returns:
            True if file contains multiuser and acl settings, False otherwise
        """
        self.debug(f"Checking screenrc file: {screenrc_path}")

        if not os.path.isfile(screenrc_path):
            self.debug(f"Screenrc file does not exist: {screenrc_path}")
            return False

        has_multiuser = False
        has_acl = False

        try:
            self.debug(f"Reading screenrc file: {screenrc_path}")
            with open(screenrc_path, "r", encoding="utf-8") as file:
                for line_num, line in enumerate(file, 1):
                    line_stripped = line.strip()
                    if line_stripped.startswith("multiuser on"):
                        self.debug(
                            f"Found 'multiuser on' at line {line_num}: {line_stripped}"
                        )
                        has_multiuser = True
                    if line_stripped.startswith("acl") or line_stripped.startswith(
                        "setacl"
                    ):
                        self.debug(
                            f"Found acl command at line {line_num}: {line_stripped}"
                        )
                        has_acl = True
        except (IOError, PermissionError) as exc:
            self.debug(f"Error reading screenrc file: {exc}")
            return False

        self.debug(f"Screenrc check results - multiuser: {has_multiuser}, acl: {has_acl}")
        return has_multiuser and has_acl

    def is_screen_suid_root(self) -> bool:
        """
        Check if the screen binary has the suid root bit set.

        Returns:
            True if screen is suid root, False otherwise
        """
        self.debug("Checking if screen binary is suid root")

        try:
            result = subprocess.run(
                ["which", "screen"], capture_output=True, text=True, check=True
            )
            screen_path = result.stdout.strip()

            self.debug(f"Screen binary path: {screen_path}")

            if not screen_path:
                self.debug("Screen path is empty")
                return False

            stat_info = os.stat(screen_path)
            mode = stat_info.st_mode
            uid = stat_info.st_uid

            self.debug(f"Screen file mode: {oct(mode)}")
            self.debug(f"Screen file uid: {uid}")

            has_suid = bool(mode & 0o4000)
            is_root_owned = uid == 0

            self.debug(f"Has suid bit: {has_suid}")
            self.debug(f"Is root owned: {is_root_owned}")

            result_bool = has_suid and is_root_owned
            self.debug(f"Screen is suid root: {result_bool}")
            return result_bool
        except (subprocess.CalledProcessError, FileNotFoundError, OSError) as exc:
            self.debug(f"Error checking screen suid status: {exc}")
            return False

    def can_attach(self, session_name: str, config: Dict[str, str]) -> bool:
        """
        Check if multiuser attachment is possible.

        Args:
            session_name: Name of the session to attach to
            config: Configuration for this session

        Returns:
            True if prerequisites are met, False otherwise
        """
        self.debug(f"Checking multiuser prerequisites for {session_name}")

        # Check if screenrc path is provided
        if "screenrc" not in config:
            self.debug("No screenrc path in config")
            return False

        screenrc_path = config["screenrc"]

        # Check screenrc for multiuser settings
        if not self.check_screenrc_multiuser(screenrc_path):
            self.debug("Screenrc does not contain proper multiuser/acl settings")
            return False

        # Check if screen is suid root
        if not self.is_screen_suid_root():
            self.debug("Screen binary is not suid root")
            return False

        self.debug("Multiuser prerequisites satisfied")
        return True

    def attach(self, session_name: str, config: Dict[str, str]) -> int:
        """
        Attempt multiuser attachment to a screen session.

        Args:
            session_name: Name of the session to attach to
            config: Configuration for this session

        Returns:
            Exit code (0 for success, 1 for failure)
        """
        print(f"Attempting multiuser attach to {session_name}")
        self.debug(f"Multiuser attach mode for session: {session_name}")
        self.debug(f"Session config: {config}")

        # Check if screenrc path is provided
        if "screenrc" not in config:
            self.debug("No screenrc path in config")
            print(
                "Error: screenrc path not configured for multiuser mode",
                file=sys.stderr,
            )
            return 1

        screenrc_path = config["screenrc"]
        self.debug(f"Using screenrc: {screenrc_path}")

        # Check screenrc for multiuser settings
        if not self.check_screenrc_multiuser(screenrc_path):
            print(
                f"Error: {screenrc_path} does not contain proper multiuser/acl settings",
                file=sys.stderr,
            )
            return 1

        # Check if screen is suid root
        if not self.is_screen_suid_root():
            print("Error: screen binary is not suid root", file=sys.stderr)
            return 1

        # Construct session ID with user
        user = config.get("user", "")
        session_id = f"{user}/gereal:{session_name}"
        self.debug(f"Constructed session ID: {session_id}")

        try:
            cmd = ["screen", "-r", session_id]
            self.debug(f"Running command: {' '.join(cmd)}")
            result = subprocess.run(cmd, check=False)
            self.debug(f"Multiuser attach returncode: {result.returncode}")
            return result.returncode
        except FileNotFoundError:
            self.debug("screen command not found in multiuser_attach")
            print("Error: screen command not found", file=sys.stderr)
            return 1


class SudoSessionHandler(BaseGerealSessionHandler):
    """Sudo-based attachment to screen sessions."""

    def get_mode_name(self) -> str:
        """Get the name of this attachment mode."""
        return "sudo"

    def can_attach(self, session_name: str, config: Dict[str, str]) -> bool:
        """
        Check if sudo attachment is possible.

        Args:
            session_name: Name of the session to attach to
            config: Configuration for this session

        Returns:
            True if sudo is configured and allowed, False otherwise
        """
        self.debug(f"Checking sudo prerequisites for {session_name}")

        if "user" not in config:
            self.debug("No user in config")
            return False

        user = config["user"]
        session_id = f"gereal:{session_name}"
        command = ["screen", "-r", session_id]

        can_sudo = self.can_sudo_as_user(user, command)
        self.debug(f"Sudo prerequisites satisfied: {can_sudo}")
        return can_sudo

    def attach(self, session_name: str, config: Dict[str, str]) -> int:
        """
        Attempt sudo-based attachment to a screen session.

        Args:
            session_name: Name of the session to attach to
            config: Configuration for this session

        Returns:
            Exit code (0 for success, 1 for failure)
        """
        print(f"Attempting sudo attach to {session_name}")
        self.debug(f"Sudo attach mode for session: {session_name}")
        self.debug(f"Session config: {config}")

        if "user" not in config:
            self.debug("No user in config")
            print("Error: user not configured for sudo mode", file=sys.stderr)
            return 1

        user = config["user"]
        session_id = f"gereal:{session_name}"
        command = ["/usr/bin/screen", "-r", session_id]

        self.debug(f"Target user: {user}")
        self.debug(f"Session ID: {session_id}")

        # Check if sudo is allowed
        if not can_sudo(command, user):
            print(
                f"Error: current user is not allowed to sudo as {user} "
                f"to run: {' '.join(command)}",
                file=sys.stderr,
            )
            return 1

        try:
            print(
                "If this asks for a password and you want to allow "
                "less access, use \n"
                "sudo visudo -f /etc/sudoers.d/gereal\n"
                "and add\n"
                f"youruser/%allowed_group ALL=({user}) NOPASSWD: {' '.join(command).replace(':', r'\:')}\n"
                "to the file"
            )
            cmd = ["sudo", "-u", user, *command]
            self.debug(f"Running command: {' '.join(cmd)}")
            result = subprocess.run(cmd, check=False)
            self.debug(f"Sudo attach returncode: {result.returncode}")
            return result.returncode
        except FileNotFoundError:
            self.debug("sudo or screen command not found in sudo_attach")
            print("Error: sudo or screen command not found", file=sys.stderr)
            return 1


class GerealManager:
    """Manages gereal screen sessions with systemd integration."""

    def __init__(self, verbose: bool = False):
        """
        Initialize the gereal manager.

        Args:
            verbose: Enable verbose debug output
        """
        self.verbose = verbose
        self.session_handlers = [
            DirectSessionHandler(verbose=verbose),
            MultiuserSessionHandler(verbose=verbose),
            SudoSessionHandler(verbose=verbose),
        ]

    def debug(self, message: str) -> None:
        """
        Print debug message if verbose mode is enabled.

        Args:
            message: Debug message to print
        """
        if self.verbose:
            print(f"[DEBUG] {message}", file=sys.stderr)

    def get_handler_by_mode(self, mode: str) -> Optional[BaseGerealSessionHandler]:
        """
        Get a session handler instance by mode name.

        Args:
            mode: Mode name (direct, multiuser, sudo)

        Returns:
            Handler instance or None if not found
        """
        for handler in self.session_handlers:
            if handler.get_mode_name() == mode:
                return handler
        return None

    def list_sessions(self, config: Dict[str, Dict[str, str]]) -> None:
        """
        List all available screen sessions from configuration.

        Args:
            config: Complete configuration dictionary
        """
        self.debug(f"Listing {len(config)} configured sessions")
        print("Available screen sessions:")
        for session_name in sorted(config.keys()):
            user = config[session_name].get("user", "current user")
            self.debug(f"Session: {session_name}, User: {user}")
            print(f"  {session_name} (user: {user})")

    def can_use_sudo(self, command: list) -> bool:
        """
        Check if current user is allowed to run a command with sudo.

        Args:
            command: Command to check (as list of arguments)

        Returns:
            True if sudo is allowed for this command, False otherwise
        """
        current_user = os.getenv("USER", "")
        self.debug(f"Checking if user {current_user} can sudo: {' '.join(command)}")

        try:
            # Use sudo -l to check if the command is allowed
            # -n means non-interactive (fail if password required)
            # -l lists the allowed commands
            result = subprocess.run(
                ["sudo", "-n", "-l"] + command,
                capture_output=True,
                text=True,
                check=False,
            )

            self.debug(f"sudo -l returncode: {result.returncode}")
            self.debug(f"sudo -l stdout: {result.stdout}")
            self.debug(f"sudo -l stderr: {result.stderr}")

            # returncode 0 means the command is allowed
            can_sudo = result.returncode == 0
            self.debug(f"Can use sudo for this command: {can_sudo}")
            return can_sudo
        except FileNotFoundError:
            self.debug("sudo command not found")
            return False

    def subprocess_run_with_sudo(self, command: list, **kwargs) -> subprocess.CompletedProcess:
        """
        Run a command, using sudo if available and needed.

        This method checks if sudo is allowed for the command. If yes, it runs
        the command with sudo. If no, it runs the command directly.

        Args:
            command: Command to run (as list of arguments)
            **kwargs: Additional arguments to pass to subprocess.run()

        Returns:
            CompletedProcess instance from subprocess.run()
        """
        self.debug(f"Attempting to run command: {' '.join(command)}")

        # First try without sudo
        try:
            self.debug("Trying command without sudo first")
            result = subprocess.run(command, check=False, **kwargs)

            # If it succeeded, we're done
            if result.returncode == 0:
                self.debug("Command succeeded without sudo")
                return result

            self.debug(f"Command failed with returncode {result.returncode}")

            # Check if it's a permission error that sudo might fix
            # Common permission error codes: 1 (general), 126 (not executable), 77 (permission denied)
            if result.returncode in (1, 77, 126):
                self.debug("Checking if sudo is available for this command")
                if self.can_use_sudo(command):
                    self.debug("Sudo is available, retrying with sudo")
                    sudo_command = ["sudo"] + command
                    self.debug(f"Running with sudo: {' '.join(sudo_command)}")
                    return subprocess.run(sudo_command, check=False, **kwargs)

            # If sudo not available or not a permission error, return original result
            self.debug("Returning original result without sudo")
            return result

        except FileNotFoundError:
            self.debug(f"Command not found: {command[0]}")
            raise

    def systemctl_command(
        self, command: str, session_name: str, config: Dict[str, str]
    ) -> int:
        """
        Execute a systemctl command for a gereal session.

        All sessions run in system context. Will use sudo if needed and allowed.

        Args:
            command: systemctl command (start, stop, status, restart, etc.)
            session_name: Name of the session
            config: Configuration for this session

        Returns:
            Exit code from systemctl
        """
        service_name = f"gereal@{session_name}.service"
        self.debug(f"Running systemctl {command} for {service_name}")

        try:
            # All sessions run in system context
            cmd = ["systemctl", command, service_name]
            self.debug(f"Preparing to run systemctl command: {' '.join(cmd)}")

            result = self.subprocess_run_with_sudo(cmd)
            self.debug(f"systemctl {command} returncode: {result.returncode}")
            return result.returncode
        except FileNotFoundError:
            self.debug("systemctl command not found")
            print("Error: systemctl command not found", file=sys.stderr)
            return 1

    def attach_session(
        self,
        session_name: str,
        config: Dict[str, Dict[str, str]],
        mode: Optional[str] = None,
    ) -> int:
        """
        Attach to a screen session.

        Args:
            session_name: Name of the session to attach to
            config: Complete configuration dictionary
            mode: Optional specific mode to use

        Returns:
            Exit code (0 for success, non-zero for failure)
        """
        self.debug(f"Attach requested for session: {session_name}")

        # Check if session is defined in config
        if session_name not in config:
            self.debug(f"Session '{session_name}' not found in configuration")
            print(
                f"Error: session '{session_name}' is not defined in configuration",
                file=sys.stderr,
            )
            return 1

        session_config = config[session_name]
        self.debug(f"Session config for '{session_name}': {session_config}")

        # Try attachment modes
        if mode:
            self.debug(f"Specific mode requested: {mode}")
            handler = self.get_handler_by_mode(mode)
            if handler:
                # Validate session is running before attempting attach
                if not handler.is_session_running(session_name):
                    return 1
                return handler.attach(session_name, session_config)
            else:
                print(f"Error: unknown mode '{mode}'", file=sys.stderr)
                return 1
        else:
            self.debug("Trying all modes in order")

            # First check if session is running (using first handler's method)
            if not self.session_handlers[0].is_session_running(session_name):
                return 1

            # Try modes in order
            for handler in self.session_handlers:
                mode_name = handler.get_mode_name()
                self.debug(f"--- Trying {mode_name} attach ---")

                result = handler.attach(session_name, session_config)
                if result == 0:
                    self.debug(f"{mode_name} attach succeeded")
                    return 0
                self.debug(f"{mode_name} attach failed with code {result}")

            self.debug("All attachment modes failed")
            return 1

    def run_command(
        self,
        command: str,
        session_name: Optional[str],
        config: Dict[str, Dict[str, str]],
        mode: Optional[str] = None,
    ) -> int:
        """
        Run a gereal command.

        Args:
            command: Command to run (attach, start, stop, status, list)
            session_name: Name of the session (required for most commands)
            config: Complete configuration dictionary
            mode: Optional specific attachment mode (for attach command)

        Returns:
            Exit code (0 for success, non-zero for failure)
        """
        self.debug("=" * 60)
        self.debug("gereal starting")
        self.debug(f"Command: {command}")
        self.debug(f"Session: {session_name}")
        self.debug(f"Current user: {os.getenv('USER', 'unknown')}")
        self.debug(f"Current UID: {os.getuid()}")
        self.debug(f"Current GID: {os.getgid()}")
        self.debug("=" * 60)

        # Handle list command
        if command == "list":
            self.debug("List command requested")
            self.list_sessions(config)
            return 0

        # All other commands require a session name
        if not session_name:
            print(f"Error: session name required for '{command}' command", file=sys.stderr)
            return 1

        # Check if session exists in config
        if session_name not in config:
            print(
                f"Error: session '{session_name}' is not defined in configuration",
                file=sys.stderr,
            )
            return 1

        session_config = config[session_name]

        # Route to appropriate handler
        if command == "attach":
            return self.attach_session(session_name, config, mode)
        elif command in ["start", "stop", "restart", "status", "enable", "disable"]:
            return self.systemctl_command(command, session_name, session_config)
        else:
            print(f"Error: unknown command '{command}'", file=sys.stderr)
            return 1


def main() -> int:
    """
    Main entry point for gereal.

    Returns:
        Exit code (0 for success, non-zero for failure)
    """
    parser = argparse.ArgumentParser(
        description="Manage screen sessions with multiple auth modes and systemd integration",
        prog="gereal",
    )

    # Add subparsers for commands
    subparsers = parser.add_subparsers(
        dest="command",
        help="Command to execute"
    )

    # Attach command
    attach_parser = subparsers.add_parser(
        "attach",
        help="Attach to a screen session"
    )
    attach_parser.add_argument(
        "session",
        help="Screen session name to attach to"
    )
    attach_parser.add_argument(
        "-m",
        "--mode",
        choices=["direct", "multiuser", "sudo"],
        help="Force specific attachment mode",
    )

    # Start command
    start_parser = subparsers.add_parser(
        "start",
        help="Start a screen session via systemd"
    )
    start_parser.add_argument(
        "session",
        help="Screen session name to start"
    )

    # Stop command
    stop_parser = subparsers.add_parser(
        "stop",
        help="Stop a screen session via systemd"
    )
    stop_parser.add_argument(
        "session",
        help="Screen session name to stop"
    )

    # Restart command
    restart_parser = subparsers.add_parser(
        "restart",
        help="Restart a screen session via systemd"
    )
    restart_parser.add_argument(
        "session",
        help="Screen session name to restart"
    )

    # Status command
    status_parser = subparsers.add_parser(
        "status",
        help="Check status of a screen session"
    )
    status_parser.add_argument(
        "session",
        help="Screen session name to check"
    )

    # List command
    subparsers.add_parser(
        "list",
        help="List available sessions"
    )

    # Global options
    parser.add_argument(
        "-v", "--verbose",
        action="store_true",
        help="Enable verbose debug output"
    )
    parser.add_argument(
        "-s", "--allow-sudo",
        action="store_true",
        help="Allow using sudo"
    )
    parser.add_argument(
        "-c",
        "--conf",
        default="/etc/gereal/gereal.conf",
        help="Path to configuration file (default: /etc/gereal/gereal.conf)",
    )

    args = parser.parse_args()

    # Show help if no command specified
    if not args.command:
        parser.print_help()
        return 1

    # Load configuration (read_config exits on error)
    config = read_config(args.conf)

    # Create manager instance with verbose setting
    manager = GerealManager(verbose=args.verbose)

    # Log configuration if verbose
    if args.verbose:
        manager.debug(f"Configuration file: {args.conf}")
        manager.debug(f"Configuration loaded: {len(config)} sessions")
        for sess_name, sess_config in config.items():
            manager.debug(f"  Session '{sess_name}': {sess_config}")

    # Run the command
    session = getattr(args, "session", None)
    mode = getattr(args, "mode", None)
    return manager.run_command(args.command, session, config, mode)


if __name__ == "__main__":
    sys.exit(main())
