#!/usr/bin/python3

##
# @file
# @ingroup rtctk_clients_ctrlMonTool
#
# @brief Entry point for the RTC Toolkit Control and Monitoring Tool.
#
# @copyright
#   SPDX-FileCopyrightText: 2023-2026 European Southern Observatory (ESO) @n
#   SPDX-License-Identifier: LGPL-3.0-only

"""
This is the entry point for the RTC Toolkit Control and Monitoring Tool that provides both a CLI and
GUI interface as an engineering tool for managing SRTC components.
"""

# The following is a workaround to prevent Taurus from hijacking the logging system.
# We trigger the setup once in Taurus, as early as possible in the process startup, by creating a
# dummy Logger object so that Taurus thinks the setup has already been done. The logging system will
# then be reconfigured later properly by the ConfigurationManager instance.
from rtctk.common.gui.logging import setup_dummy_taurus_logger

setup_dummy_taurus_logger()

# The following is needed to skip unit tests that are failing due to problems in DevEnv.
# See https://jira.eso.org/browse/ELTDEV-1172
import pytest  # noqa: E402

_FRAMEWORK_MODULE_ = pytest.importorskip("rtctk.framework")

import sys  # noqa: E402
import os  # noqa: E402
import click  # noqa: E402
from elt.log import CiiLogManager, CiiLogMessageBuilder  # noqa: E402
from rtctk.common.cli.config import ConfigurationManager  # noqa: E402
from rtctk.common.cli.exceptions import format_error_message  # pylint: disable=E0611  # noqa: E402
from rtctk.common.cli.argtypes import LogLevel, UriParamType  # noqa: E402
from rtctk.ctrlmontool.comms.config import ConfigurationParameters  # noqa: E402
from rtctk.framework import (  # pylint: disable=E0611,E0401  # noqa: E402
    DataPointPath,
    RtctkException,
)

__version__ = "6.0.0"


_config_manager = ConfigurationManager(
    ConfigurationParameters, DataPointPath("/rtctk_ctrl_mon_tool")
)
_defaults = _config_manager.get_config_defaults()


# Prevent Doxygen from trying to produce API documentation for the click CLI handling methods.
# The docstring comments will not be appropriate. Click's formatting tags can clash with Doxygen.
# And these methods anyway do not form part of the API.
## @cond __doxygen_ignore__


# IMPORTANT: Do not add parameter or return descriptions in the docstrings for click annotated
#            methods. The docstrings are used by click to generate help output for the CLI.
@click.group()
@click.version_option(version=__version__, message="%(version)s")
@click.option(
    "--config",
    type=UriParamType(),
    help="URI indicating the location of this tool's configuration. This can either be a URI to a"
    " location within the CII configuration service or to local files.",
)
@click.option(
    "--service-discovery-endpoint",
    "--sde",
    type=UriParamType(),
    help="URI endpoint to the service discovery. This overrides any"
    f" {_defaults.service_discovery_endpoint.path} setting found in the configuration."
    f" Default: {_defaults.service_discovery_endpoint.value}",
)
@click.option(
    "--log-level",
    type=click.Choice(LogLevel),
    help="Sets the global logging level for the tool. This overrides any"
    f" {_defaults.log_level.path} setting found in the configuration."
    f" Default: {_defaults.log_level.value}",
)
@click.option(
    "--log-to-file",
    is_flag=True,
    flag_value=True,
    help=f"Makes the tool log to file. This overrides any {_defaults.log_to_file.path} setting"
    f" found in the configuration. Default: {_defaults.log_to_file.value}",
)
@click.option(
    "--deployment-daemon-name",
    "--ddn",
    help="Optional Deployment Daemon Name. This overrides any"
    f" {_defaults.deployment_daemon_name.path} setting found in the configuration."
    f" Default: {_defaults.deployment_daemon_name.value}",
)
@click.option(
    "--connect-timeout",
    "-T",
    type=click.INT,
    help=f"connect timeout [msec]. This overrides any {_defaults.connect_timeout.path} setting"
    f" found in the configuration. Default: {_defaults.connect_timeout.value}",
)
@click.option(
    "--reply-timeout",
    "-t",
    type=click.INT,
    help=f"reply timeout [msec]. This overrides any {_defaults.reply_timeout.path} setting"
    f" found in the configuration. Default: {_defaults.reply_timeout.value}",
)
@click.pass_context
def main(
    ctx,
    config,
    service_discovery_endpoint,
    log_level,
    log_to_file,
    deployment_daemon_name,
    connect_timeout,
    reply_timeout,
):
    """
    RTC Control and Monitoring Tool.

    This is used to control and monitor RTC Component applications in a basic manner.
    """
    # The only thing we do here is update the configuration with the arguments parsed by click.
    # Note: Do not perform any logic that might log at this stage. We are still parsing the command
    # line here and should only be bookkeeping, e.g. recording what arguments were used.
    # This is important to avoid polluting the command line help messages or slowing down the
    # program when only the help or version were requested. Real operation logic should be deferred
    # to the leaf sub-commands when we are done with command line parsing.
    _config_manager.update_config_from_cli(**ctx.params)


@main.command()
def gui():
    """
    Start the graphical application.

    Graphical Application that provides an overview of the RTC Components
    deployed and access to their interfaces.
    """
    _config_manager.load_config()

    if "CONFIG_CLIENT_INI" in os.environ:
        CiiLogManager.get_logger().debug(f"CONFIG_CLIENT_INI={os.environ['CONFIG_CLIENT_INI']}")

    # We perform the import here instead of the top-level to avoid the overhead if we are not
    # actually running the GUI.
    import rtctk.ctrlmontool.gui

    # pylint: disable=E1101
    rtctk.ctrlmontool.gui.gui_main(_config_manager.get_active_config_as_dataclass(), __version__)


@main.command()
@click.argument("component", type=str)
@click.argument("command", type=str)
@click.argument("args", required=False, type=str)
@click.pass_context
def send(ctx, component: str, command: str, args: str):
    """
    RTC Control and Monitoring Tool send command.

    Requests the execution of COMMAND to the COMPONENT.
    If defined, ARGS will be passed as arguments.

    NOT IMPLEMENTED
    """
    raise NotImplementedError("'send' command not implemented yet.")


@main.command()
@click.argument("component", type=str)
@click.pass_context
def state(ctx, component: str):
    """
    RTC Control and Monitoring Tool state command.

    Queries and prints the state of the COMPONENT.

    NOT IMPLEMENTED
    """
    raise NotImplementedError("'state' command not implemented yet.")


## @endcond


if __name__ == "__main__":
    # Note that we do not setup the logging subsystem here. We defer this step to the
    # ConfigurationManager instance (_config_manager) that takes care of any intricacies of setting
    # up the logging system and loading the applicable configuration. By the time the logger is
    # fetched in the exception handling code below, the logging system should already be correctly
    # setup by _config_manager.
    try:
        main(default_map=_config_manager.get_config_defaults_as_dict())  # pylint: disable=E1120
    except (RuntimeError, RtctkException) as err:
        logger = CiiLogManager.get_logger()
        logger.error(f"{format_error_message(err)}")
        print(f"ERROR: {format_error_message(err)}", file=sys.stderr)
        sys.exit(1)
    except Exception as err:
        # Unexpected exceptions, i.e. anything that is not explicitly a RuntimeError, are assumed to
        # be code bugs, so we log it and rethrow.
        logger = CiiLogManager.get_logger()
        logger.fatal(
            CiiLogMessageBuilder.create_and_build_with_exception(
                "An unexpected exception occurred", err
            )
        )
        raise
