import contextlib
import importlib.util
import pathlib
import sys
import typing

import colorama

import chitragupt._about as about
import chitragupt.abc.generic as abc_generic
import chitragupt.core.kundalini as kundalini
import chitragupt.core.logger as logger
import chitragupt.core.managers as manager
import chitragupt.core.plugin as plugin
import chitragupt.exceptions as exceptions
import chitragupt.ext.dot_cache as dot_cache
import chitragupt.ext.git_operator as git_operator
import chitragupt.modules.browsers as browser
import chitragupt.modules.browsers.duckduckgo as duckduckgo
import chitragupt.modules.browsers.google as google
import chitragupt.modules.browsers.thirteentabs as thirteentabs

# Type aliases from the generic module
ChitraGuptParameter = abc_generic.ChitraGuptParameter
ChitraguptCommand = abc_generic.ChitraguptCommand

# Logger and related types
GuptLogger = logger.GuptLogger
LogType = logger.LogType

# Plugin class and DataType (use the enum defined in plugin)
ChitraguptPlugin = plugin.ChitraguptPlugin
DataType = abc_generic.DataType
BrowserType = browser.BrowserType


class ChitraGuptClientCLI:
    """
    The core CLI client for ChitraGupt.

    This class manages the execution of system and plugin commands, plugin
    loading, caching, logging, and the interactive prompt loop. It also stores
    the last executed command string as an attribute for further inspection.

    Attributes
    ----------
    logger : GuptLogger
        Logger instance for logging messages.
    _system_commands : List[ChitraguptCommand]
        Reserved system commands.
    _registered_command : List[ChitraguptCommand]
        User-registered commands.
    _registered_plugins : List[ChitraguptPlugin]
        List of registered plugins.
    _plugin_lock : bool
        Indicates if a plugin is currently locked for exclusive command execution.
    current_plugin : Optional[ChitraguptPlugin]
        The currently active plugin.
    command_string : str
        The last command string entered by the user.
    poll : bool
        Flag to control the interactive prompt loop.
    plugin_manager : manager.PluginManager
        The integrated plugin manager.
    web_client : BrowserBase
        A web client instance for browsing operations.
    """

    def __init__(
        self,
        logger: GuptLogger,
        enable_dot_cache: typing.Optional[dot_cache.CachePath] = None,
        enable_kundalini: typing.Optional[kundalini.CrawlerConfig] = None,
        enable_osint_util: typing.Optional[
            typing.List[typing.Literal["image", "text", "url"]]
        ] = None,
        enable_browsers: typing.Optional[
            typing.List[typing.Literal["duckduckgo", "thirteentabs", "google"]]
        ] = None,
        enable_general_browser: typing.Optional[browser.BrowserBase] = None,
        enable_core_util: typing.Optional[bool] = False,
        enable_cache: typing.Optional[bool] = False,
        custom_git_operator: typing.Optional[git_operator.GitIO] = None,
        enable_dorkinator: typing.Optional[bool] = False,
    ) -> None:
        """
        Initialize the ChitraGupt CLI client.

        Parameters
        ----------
        logger : GuptLogger
            Logger instance for logging messages.
        enable_kundalini : Optional[kundalini.CrawlerConfig]
            Configuration for enabling the kundalini crawler.
        enable_osint_util : Optional[List[Literal["image", "text", "url"]]]
            List of OSINT utilities to enable.
        enable_browsers : Optional[List[Literal["duckduckgo", "thirteentabs", "google"]]]
            List of browser types to enable.
        enable_general_browser : Optional[BrowserBase], optional
            A general browser instance to enable, by default None.
        enable_core_util : bool, optional
            Flag to enable core utilities, by default False.
        enable_cache : bool, optional
            Flag to enable caching, by default False.
        enable_dot_cache : Optional[dot_cache.CachePath], optional
            Dot cache path to enable, by default dot_cache.GLOBAL_CAHCHE.
        custom_git_operator : Optional[git_opeartor.GitIO], optional
            Custom git operator instance, by default None.
        enable_dorkinator : bool, optional
            Flag to enable the dorkinator, by default False.
        """
        self._display_banner()
        self.logger: GuptLogger = logger

        self._system_commands: typing.List[ChitraguptCommand] = []
        self.logger.log(
            message="Initialized empty list for system reserved commands.",
            log_type=LogType.DEBUG,
        )
        self._registered_command: typing.List[ChitraguptCommand] = []
        self.logger.log(
            message="Initialized empty list for registered commands.",
            log_type=LogType.DEBUG,
        )
        self._registered_plugins: typing.List[ChitraguptPlugin] = []
        self.logger.log(
            message="Initialized empty list for plugins.",
            log_type=LogType.DEBUG,
        )
        self._plugin_lock: bool = False
        self.logger.log(
            message="Plugin lock set to False.",
            log_type=LogType.DEBUG,
        )
        self.current_plugin: typing.Optional[ChitraguptPlugin] = None
        self.logger.log(
            message="Current plugin set to None.",
            log_type=LogType.DEBUG,
        )
        self.command_string: str = ""
        self.logger.log(
            message="Command string initialized as empty.",
            log_type=LogType.DEBUG,
        )
        self.poll: bool = True
        self.logger.log(
            message="Polling started.",
            log_type=LogType.DEBUG,
        )
        self.plugin_manager: manager.PluginManager = manager.PluginManager(
            logger=self.logger
        )
        # Initialize the web client with a default browser type.
        self.web_client: browser.BrowserBase = browser.BrowserBase(
            impersonate=BrowserType.chrome120
        )
        self.logger.log(
            message="Web client created with default impersonation (chrome120).",
            log_type=LogType.DEBUG,
        )
        if enable_kundalini:
            self.kundalini = kundalini.Kundalini(
                config=enable_kundalini, logger=self.logger
            )
            self.logger.log(
                message="Kundalini crawler initialized.",
                log_type=LogType.DEBUG,
            )
        if enable_general_browser:
            self.gbrowse = browser.BrowserBase(logger=self.logger)
            self.logger.log(
                message="General browser initialized.",
                log_type=LogType.DEBUG,
            )
        if enable_core_util:
            import chitragupt.core.utils as utils

            self.core_util = utils.OSINTUtilityTool()
            self.logger.log(
                message="Core utilities enabled.",
                log_type=LogType.DEBUG,
            )
        if enable_osint_util:
            if "image" in enable_osint_util:
                import chitragupt.modules.osint.image as osint_image

                self.osint_image = osint_image.ImageUtils(logger=self.logger)
                self.logger.log(
                    message="OSINT image utility enabled.",
                    log_type=LogType.DEBUG,
                )
            if "text" in enable_osint_util:
                import chitragupt.modules.osint.text as osint_text

                self.osint_text = osint_text.TextUtils()
                self.logger.log(
                    message="OSINT text utility enabled.",
                    log_type=LogType.DEBUG,
                )
            if "url" in enable_osint_util:
                import chitragupt.modules.osint.urls as osint_urls

                self.osint_url = osint_urls.URLUtils()
                self.logger.log(
                    message="OSINT URL utility enabled.",
                    log_type=LogType.DEBUG,
                )
        if enable_dorkinator is True:
            import chitragupt.modules.misc.dorkinator as dorkinator

            self.dorkinator = dorkinator.GoogleDorkBuilder()
            self.logger.log(
                message="Dorkinator enabled.",
                log_type=LogType.DEBUG,
            )
        if enable_cache is True:
            import chitragupt.core.cache as cache

            self.cache = cache.GuptCache()
            self.cache.init(internal_logger=self.logger)
            self.logger.log(
                message="Cache initialized.",
                log_type=LogType.DEBUG,
            )
        if enable_dot_cache is not None:
            self.dot_cache = dot_cache.DotCache(path=enable_dot_cache)
            self.logger.log(
                message="Dot cache initialized.",
                log_type=LogType.DEBUG,
            )
        if custom_git_operator:
            self.git_operator = custom_git_operator
            self.logger.log(
                message="Custom git operator enabled.",
                log_type=LogType.DEBUG,
            )
        if enable_browsers:
            if "duckduckgo" in enable_browsers:
                self.duckduckgo = duckduckgo.DuckDuckGo(logger=self.logger)
                self.logger.log(
                    message="DuckDuckGo initialized successfully",
                    log_type=LogType.DEBUG,
                )
            if "google" in enable_browsers:
                self.google = google.Google(logger=self.logger)
                self.logger.log(
                    message="Google initialized successfully",
                    log_type=LogType.DEBUG,
                )
            if "thirteentabs" in enable_browsers:
                self.thirteentabs = thirteentabs.ThirteenTabs(
                    logger=self.logger
                )
                self.logger.log(
                    message="ThirteenTabs initialized successfully",
                    log_type=LogType.DEBUG,
                )
            self.logger.log(
                message="Browsers enabled.",
                log_type=LogType.DEBUG,
            )

    @staticmethod
    def _display_banner() -> None:
        """
        Display the ChitraGupt banner on startup.

        Reads the banner from 'chitragupt/assets/banner.txt', applies color formatting
        (including version information), and logs it using the logger.

        Returns
        -------
        None
        """
        with open(
            "chitragupt/assets/banner.txt", "r", encoding="utf-8"
        ) as banner_file:
            raw_banner: str = banner_file.read()
        banner: str = (
            colorama.Style.BRIGHT
            + raw_banner.format(
                chitra=colorama.Fore.GREEN,
                RESET=colorama.Fore.RESET,
                gupta=colorama.Fore.LIGHTRED_EX,
                version=about.__version__,
                quote=colorama.Fore.LIGHTCYAN_EX,
            )
            + colorama.Style.RESET_ALL
        )
        print(banner)

    def _make_prompt(self) -> str:
        """
        Construct and display the interactive prompt.

        Applies color formatting and appends the active plugin name if one is locked.
        Captures user input as a command string and updates the framework attribute.

        Returns
        -------
        str
            The command entered by the user.
        """
        if not self.poll:
            return ""
        prompt_text: str = (
            colorama.Fore.LIGHTGREEN_EX
            + "Chitra"
            + colorama.Fore.RESET
            + " "
            + colorama.Fore.LIGHTRED_EX
            + "गुप्त"
            + colorama.Fore.RESET
            + " "
            + colorama.Fore.MAGENTA
            + "[>]"
            + colorama.Fore.RESET
            + " "
        )
        if self._plugin_lock and self.current_plugin:
            prompt_text += (
                colorama.Fore.LIGHTCYAN_EX
                + f"[{self.current_plugin.name}] "
                + colorama.Fore.RESET
            )
        # Input is required for interactive prompt.
        user_input: str = input(prompt_text)
        self.command_string = user_input
        self.logger.log(
            message=f"Received command input: '{user_input}'",
            log_type=LogType.DEBUG,
        )
        return user_input

    def handle_exit(self) -> None:
        """
        Handle a clean exit of the CLI client.

        Releases any active plugin lock if present; otherwise, stops polling and exits.

        Returns
        -------
        None
        """
        if self._plugin_lock:
            self.logger.log(
                message="Releasing plugin lock.",
                log_type=LogType.DEBUG,
            )
            self._plugin_lock = False
            self.current_plugin = None
            self.logger.log(
                message="Current plugin reset to None.",
                log_type=LogType.DEBUG,
            )
        else:
            self.logger.log(
                message="Exiting CLI client. Terminating execution.",
                log_type=LogType.DEBUG,
            )
            self.poll = False
            sys.exit()

    def _setup_plugin(self, plugin: ChitraguptPlugin) -> None:
        """
        Set up and activate a plugin by enabling the plugin lock.

        Parameters
        ----------
        plugin : ChitraguptPlugin
            The plugin instance to activate.

        Returns
        -------
        None
        """
        self._plugin_lock = True
        self.current_plugin = plugin
        self.logger.log(
            message=f"Plugin lock enabled. Current plugin set to '{plugin.name}'.",
            log_type=LogType.DEBUG,
        )

    @staticmethod
    def _detect_data_type(value: str) -> DataType:
        """
        Detect the data type of a given string value.

        Parameters
        ----------
        value : str
            The string value to analyze.

        Returns
        -------
        DataType
            One of DataType.INTEGER, DataType.FLOAT, DataType.BOOL, or DataType.STRING.
        """
        with contextlib.suppress(ValueError):
            int(value)
            return DataType.INTEGER
        with contextlib.suppress(ValueError):
            float(value)
            return DataType.FLOAT
        return (
            DataType.BOOL
            if value.lower() in {"true", "false"}
            else DataType.STRING
        )

    @staticmethod
    def _cast_value(value: str, data_type: DataType) -> typing.Any:
        """
        Cast a string value to the specified data type.

        Parameters
        ----------
        value : str
            The string representation of the value.
        data_type : DataType
            Target type: DataType.INTEGER, DataType.FLOAT, DataType.BOOL, or DataType.STRING,
            or their optional variants (DataType.OINTEGER, DataType.OFLOAT, DataType.OBOOL, DataType.OSTRING).

        Returns
        -------
        Any
            The value cast to the specified type.
        """
        if data_type in {DataType.INTEGER, DataType.OINTEGER}:
            return int(value)
        elif data_type in {DataType.FLOAT, DataType.OFLOAT}:
            return float(value)
        elif data_type in {DataType.BOOL, DataType.OBOOL}:
            return value.lower() == "true"
        # For STRING and OSTRING, no conversion is needed.
        return value

    def _parse_arguments(
        self, command_string: str
    ) -> typing.List[abc_generic.ChitraGuptParameter]:
        """
        Parse a command string into structured parameter values.

        Extracts arguments prefixed with '--', detects their data types (using the DataType enum),
        casts them accordingly, and attaches the casted value to each parameter instance.

        Parameters
        ----------
        command_string : str
            The raw command string.

        Returns
        -------
        List[ChitraGuptParameter]
            List of parsed parameter values, each with an attached 'value' attribute.

        Raises
        ------
        ValueError
            If an argument does not follow the expected '--name value' format.
        """
        # Use DataType enum names for validation.
        valid_data_types: typing.Set[str] = {dt.name for dt in DataType}
        args = command_string.split("--")[1:]
        parsed_params: typing.List[abc_generic.ChitraGuptParameter] = []
        for arg in args:
            parts = arg.strip().split(" ", 1)
            if len(parts) != 2:
                raise ValueError(
                    f"Invalid argument format: '{arg}'. Expected '--name value'."
                )
            key, value = parts
            detected_type: DataType = self._detect_data_type(value)
            if detected_type.name not in valid_data_types:
                raise ValueError(
                    f"Invalid detected type '{detected_type}' for argument '{key}'."
                )
            casted_value = self._cast_value(value, detected_type)
            # Determine optionality based on the DataType's name.
            is_optional: bool = detected_type.name.startswith("O")
            param = abc_generic.ChitraGuptParameter(
                name=key.strip(),
                data_type=detected_type,
                optional=is_optional,
            )
            # Dynamically attach the parsed (cast) value to the parameter.
            setattr(param, "value", casted_value)
            parsed_params.append(param)
        return parsed_params

    @staticmethod
    def _check_parameters(
        params: typing.List[abc_generic.ChitraGuptParameter],
        command: ChitraguptCommand,
    ) -> bool:
        """
        Validate provided parameters against expected command parameters.

        Ensures required parameters are present and of the correct type, and that no unexpected
        parameters are provided.

        Parameters
        ----------
        params : List[ChitraGuptParameter]
            The provided parameter values.
        command : ChitraguptCommand
            The command definition with expected parameters.

        Returns
        -------
        bool
            True if parameters are valid.

        Raises
        ------
        ValueError
            If a required parameter is missing or an unexpected parameter is provided.
        NameError
            If parameter names do not match.
        TypeError
            If parameter types do not match.
        """
        provided = {p.name: p for p in params}
        expected = {p.name: p for p in command.parameters}

        for name, exp_param in expected.items():
            if name not in provided:
                if not exp_param.optional:
                    raise ValueError(f"Missing required parameter: '{name}'.")
                continue
            prov_param = provided[name]
            if prov_param.name != exp_param.name:
                raise NameError(
                    f"Parameter name mismatch: expected '{exp_param.name}', got '{prov_param.name}'."
                )
            if not (
                prov_param.data_type == exp_param.data_type
                or (
                    prov_param.data_type == "STRING"
                    and exp_param.data_type == "OSTRING"
                )
                or (
                    prov_param.data_type == "INTEGER"
                    and exp_param.data_type == "OINTEGER"
                )
                or (
                    prov_param.data_type == "FLOAT"
                    and exp_param.data_type == "OFLOAT"
                )
                or (
                    prov_param.data_type == "BOOL"
                    and exp_param.data_type == "OBOOL"
                )
            ):
                raise TypeError(
                    f"Parameter '{prov_param.name}' has incorrect type: expected '{exp_param.data_type}', got '{prov_param.data_type}'."
                )
        unexpected = [p.name for p in params if p.name not in expected]
        if unexpected:
            raise ValueError(
                f"Unexpected parameters provided: {', '.join(unexpected)}."
            )
        return True

    def add_plugin(self, plugin: ChitraguptPlugin) -> None:
        """
        Register a new plugin with the CLI framework.

        Parameters
        ----------
        plugin : ChitraguptPlugin
            The plugin instance to register.

        Returns
        -------
        None
        """
        self._registered_plugins.append(plugin)
        self.logger.log(
            message=f"Plugin '{plugin.name}' registered.",
            log_type=LogType.DEBUG,
        )

    def load_plugins_from(
        self, path_to_plugins: typing.Union[str, pathlib.Path]
    ) -> None:
        """
        Load all plugins from the specified directory.

        Iterates through the directory and attempts to load each Python file as a plugin.

        Parameters
        ----------
        path_to_plugins : Union[str, pathlib.Path]
            The directory containing plugin files.

        Returns
        -------
        None

        Raises
        ------
        ChitraGuptPathError
            If the provided path is not a valid directory.
        """
        path = pathlib.Path(path_to_plugins).resolve()
        if not path.is_dir():
            raise exceptions.ChitraGuptPathError(f"Invalid directory: {path}")
        for plugin_file in path.iterdir():
            if plugin_file.is_file() and plugin_file.suffix == ".py":
                try:
                    self.load_plugin(plugin_file)
                except Exception as e:
                    self.logger.log(
                        message=f"Failed to load plugin {plugin_file}: {e}",
                        log_type=LogType.ERROR,
                    )

    def load_plugin(
        self, path_to_plugin: typing.Union[str, pathlib.Path]
    ) -> None:
        """
        Load a plugin from the specified file path.

        Dynamically imports the module and executes its `load` function.

        Parameters
        ----------
        path_to_plugin : Union[str, pathlib.Path]
            The file path to the Python plugin.

        Returns
        -------
        None

        Raises
        ------
        ChitraGuptPathError
            If the path is not a valid Python file or the module cannot be imported.
        ChitraGuptLoadFunctionNotFoundError
            If the module lacks a callable `load` function.
        """
        path_obj = pathlib.Path(path_to_plugin).resolve()
        if not path_obj.is_file() or path_obj.suffix != ".py":
            raise exceptions.ChitraGuptPathError(
                f"Invalid Python file: {path_obj}"
            )
        module_name = path_obj.stem
        spec = importlib.util.spec_from_file_location(module_name, path_obj)
        if spec and spec.loader:
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            if not hasattr(module, "load") or not callable(
                getattr(module, "load")
            ):
                raise exceptions.ChitraGuptLoadFunctionNotFoundError(
                    f"Module {module_name} lacks a callable `load` function."
                )
            self.logger.log(
                message=f"Successfully loaded 'load' function from {path_obj}.",
                log_type=LogType.DEBUG,
            )
            getattr(module, "load")(self, "cmd_str")
        else:
            raise exceptions.ChitraGuptPathError(
                f"Could not import module from {path_obj}"
            )

    def use_plugin(self, plugin: str) -> None:
        """
        Activate a specified plugin for exclusive command execution.

        Parameters
        ----------
        plugin : str
            The name of the plugin to activate.

        Returns
        -------
        None
        """
        self.logger.log(
            message=f"Attempting to use plugin: '{plugin}'.",
            log_type=LogType.DEBUG,
        )
        if self._plugin_lock:
            if self.current_plugin:
                self.logger.log(
                    message=f"Plugin lock is active. Current plugin: '{self.current_plugin.name}'.",
                    log_type=LogType.ERROR,
                )
            else:
                self.logger.log(
                    message="Plugin lock is active, but no current plugin is set.",
                    log_type=LogType.ERROR,
                )
            return

        for p in self._registered_plugins:
            if p.name.lower() == plugin.lower():
                self.current_plugin = p
                self._plugin_lock = True
                self.logger.log(
                    message=f"Plugin '{p.name}' activated with plugin lock enabled.",
                    log_type=LogType.DEBUG,
                )
                return
        self.logger.log(
            message=f"No plugin found with name '{plugin}'.",
            log_type=LogType.ERROR,
        )

    def unload_plugin(self, plugin: str) -> None:
        """
        Unload the specified plugin from the client entirely.

        This method removes the plugin (by name, case-insensitive) from the client's
        list of registered plugins. If the plugin is currently active, its lock is also
        released and the current plugin is reset to None.

        Parameters
        ----------
        plugin : str
            The name of the plugin to unload.

        Returns
        -------
        None

        Examples
        --------
        >>> client_instance.unload_plugin("example_plugin")
        """
        plugin_found = False
        for index, p in enumerate(self._registered_plugins):
            if p.name.lower() == plugin.lower():
                plugin_found = True
                # If this plugin is currently active, clear the plugin lock and current plugin.
                if (
                    self.current_plugin is not None
                    and self.current_plugin.name.lower() == plugin.lower()
                ):
                    self._plugin_lock = False
                    self.current_plugin = None
                    self.logger.log(
                        message=f"Active plugin '{plugin}' was deactivated before unloading.",
                        log_type=LogType.INFO,
                    )
                del self._registered_plugins[index]
                self.logger.log(
                    message=f"Plugin '{plugin}' has been unloaded from the client.",
                    log_type=LogType.INFO,
                )
                break
        if not plugin_found:
            self.logger.log(
                message=f"Plugin '{plugin}' was not found among registered plugins.",
                log_type=LogType.ERROR,
            )

    def _execute_command(
        self,
        cmd: ChitraguptCommand,
        args: typing.List[abc_generic.ChitraGuptParameter],
        command_string: str,
    ) -> None:
        """
        Execute a given command with parsed arguments while logging the execution.

        Parameters
        ----------
        cmd : ChitraguptCommand
            The command to execute.
        args : List[ChitraGuptParameter]
            The parsed parameter values for the command. Each parameter is expected to have
            a 'value' attribute containing the casted value.
        command_string : str
            The original command string entered by the user.

        Returns
        -------
        None
        """
        # Build a dictionary of parameter names to their values.
        command_params = {
            a.name: getattr(a, "value", None)
            for a in args
            if not (a.optional and getattr(a, "value", None) is None)
        }
        self.logger.log(
            message=f"Executing command '{cmd.name}' with parameters {command_params} and command string '{self.command_string}'.",
            log_type=LogType.DEBUG,
        )
        try:
            cmd.function(
                framework=self, cmd_str=command_string, **command_params
            )  # type: ignore
            self.logger.log(
                message=f"Successfully executed command '{cmd.name}' with command string '{command_string}'.",
                log_type=LogType.DEBUG,
            )
        except Exception as e:
            self.logger.log(
                message=f"Error executing command '{cmd.name}' with command string '{command_string}': {e}",
                log_type=LogType.ERROR,
            )

    def start(self) -> None:
        """
        Start the CLI command execution loop.

        Loads system commands and other necessary resources, then continuously polls for user input.
        Commands are executed from either plugin commands (if a plugin is locked) or system/registered commands.

        Returns
        -------
        None
        """
        # Load system commands from the designated folder.
        self.load_plugins_from("chitragupt/modules/sysops")
        self.logger.log(
            message="System commands loaded and cache factory initialized.",
            log_type=LogType.DEBUG,
        )

        while self.poll:
            try:
                command_str: str = self._make_prompt()
                command_executed: bool = False

                # Execute plugin command if a plugin lock is active.
                if self._plugin_lock and self.current_plugin:
                    for cmd in self.current_plugin.commands:
                        if command_str.startswith(cmd.name):
                            args = self._parse_arguments(command_str)
                            self._check_parameters(
                                args,
                                cmd,  # type: ignore
                            )  # Validate parameters. #type: ignore
                            self._execute_command(
                                cmd=cmd,  # type: ignore
                                args=args,
                                command_string=command_str,
                            )  # type: ignore
                            command_executed = True
                            break

                # Execute system/registered command if no plugin command was executed.
                if not command_executed:
                    for cmd in (
                        self._registered_command + self._system_commands
                    ):
                        if command_str.startswith(cmd.name):
                            args = self._parse_arguments(command_str)
                            self._check_parameters(
                                args, cmd
                            )  # Validate parameters.
                            self._execute_command(
                                cmd=cmd, args=args, command_string=command_str
                            )
                            command_executed = True
                            break

                if not command_executed:
                    self.logger.log(
                        message=f"Invalid command: '{command_str}'.",
                        log_type=LogType.ERROR,
                    )
            except KeyboardInterrupt:
                self.logger.log(
                    message="Keyboard interrupt received. Exiting CLI loop.",
                    log_type=LogType.INFO,
                )
                break
            except (ValueError, NameError, TypeError) as err:
                self.logger.log(
                    message=f"Parameter or command error: {err}",
                    log_type=LogType.ERROR,
                )
            except Exception as exc:
                self.logger.log(
                    message=f"Unexpected error during command execution: {exc}",
                    log_type=LogType.ERROR,
                )

    @property
    def system_commands(self) -> typing.List[ChitraguptCommand]:
        """
        Get the list of system reserved commands.

        Returns
        -------
        List[ChitraguptCommand]
            The system reserved commands.
        """
        return self._system_commands
