"""Control Server for the Ariel Telescope Control Unit (TCU)."""

import logging
import multiprocessing
from typing import Annotated

import rich
import sys
import typer
import zmq

from egse.ariel.tcu import (
    COMMANDING_PORT,
    HOSTNAME,
    PROTOCOL,
    SERVICE_PORT,
    MONITORING_PORT,
    STORAGE_MNEMONIC,
    PROCESS_NAME,
    SERVICE_TYPE,
)
from egse.ariel.tcu.tcu import TcuProxy
from egse.ariel.tcu.tcu_protocol import TcuProtocol
from egse.control import is_control_server_active, ControlServer
from egse.registry.client import RegistryClient
from egse.services import ServiceProxy
from egse.storage import store_housekeeping_information
from egse.zmq_ser import connect_address, get_port_number

logger = logging.getLogger("egse.ariel.tcu")


def is_tcu_cs_active(timeout: float = 0.5) -> bool:
    """Checks whether the Ariel TCU Control Server is running.

    Args:
        timeout (float): Timeout when waiting for a reply [s].

    Returns:
        True if the Ariel TCU Control Server is running and replied with the expected answer; False otherwise.
    """

    if COMMANDING_PORT != 0:
        protocol = PROTOCOL
        hostname = HOSTNAME
        port = COMMANDING_PORT

    else:
        with RegistryClient() as reg:
            service = reg.discover_service(SERVICE_TYPE)

            if service:
                protocol = service.get("protocol", "tcp")
                hostname = service["host"]
                port = service["port"]

            else:
                return False

    # noinspection PyUnboundLocalVariable
    endpoint = connect_address(protocol, hostname, port)

    return is_control_server_active(endpoint, timeout)


class TcuControlServer(ControlServer):
    def __init__(self, simulator: bool = False):
        """Initialisation of an Ariel TCU Control Server.

        Args:
            simulator (bool): Indicates whether to operate in simulator mode.
        """

        super().__init__()

        multiprocessing.current_process().name = PROCESS_NAME

        self.logger = logger
        self.service_name = PROCESS_NAME
        self.service_type = SERVICE_TYPE

        self.device_protocol = TcuProtocol(self, simulator=simulator)

        self.logger.info(f"Binding ZeroMQ socket to {self.device_protocol.get_bind_address()}")

        self.device_protocol.bind(self.dev_ctrl_cmd_sock)

        self.poller.register(self.dev_ctrl_cmd_sock, zmq.POLLIN)

        self.register_service(SERVICE_TYPE)

    def get_communication_protocol(self) -> str:
        """Returns the communication protocol used by the Ariel TCU Control Server.

        Returns:
            Communication protocol used by the Ariel TCU Control Server, as specified in the settings.
        """

        return PROTOCOL

    def get_commanding_port(self) -> int:
        """Returns the commanding port used by the Ariel TCU Control Server.

        Returns:
            Commanding port used by the Ariel TCU Control Server, as specified in the settings.
        """

        return get_port_number(self.dev_ctrl_cmd_sock) or COMMANDING_PORT

    def get_service_port(self) -> int:
        """Returns the service port used by the Ariel TCU Control Server.

        Returns:
            Service port used by the Ariel TCU Control Server, as specified in the settings.
        """

        return get_port_number(self.dev_ctrl_service_sock) or SERVICE_PORT

    def get_monitoring_port(self) -> int:
        """Returns the monitoring port used by the Ariel TCU Control Server.

        Returns:
            Monitoring port used by the Ariel TCU Control Server, as specified in the settings.
        """

        return get_port_number(self.dev_ctrl_mon_sock) or MONITORING_PORT

    def get_storage_mnemonic(self) -> str:
        """Returns the storage mnemonic used by the Ariel TCU Control Server.

        Returns:
            Storage mnemonic used by the Ariel TCU Control Server, as specified in the settings.
        """

        return STORAGE_MNEMONIC

    def is_storage_manager_active(self):
        """Checks whether the Storage Manager is active."""
        from egse.storage import is_storage_manager_active

        return is_storage_manager_active()

    def store_housekeeping_information(self, data):
        """Sends housekeeping information of the Ariel TCU to the Storage Manager."""

        origin = self.get_storage_mnemonic()
        store_housekeeping_information(origin, data)

    def register_to_storage_manager(self):
        from egse.storage import register_to_storage_manager
        from egse.storage.persistence import TYPES

        register_to_storage_manager(
            origin=self.get_storage_mnemonic(),
            persistence_class=TYPES["CSV"],
            prep={
                "column_names": list(self.device_protocol.get_housekeeping().keys()),
                "mode": "a",
            },
        )

    def unregister_from_storage_manager(self):
        from egse.storage import unregister_from_storage_manager

        unregister_from_storage_manager(origin=self.get_storage_mnemonic())

    # def before_serve(self):
    #     start_http_server(CTRL_SETTINGS.METRICS_PORT)

    def after_serve(self):
        self.deregister_service()


app = typer.Typer()


@app.command()
def start(
    simulator: Annotated[
        bool, typer.Option("--simulator", "--sim", help="start the Ariel TCU Control Server in simulator mode")
    ] = False,
):
    """Starts the Ariel TCU Control Server."""

    with remote_logging():
        from egse.env import setup_env

        setup_env()

        # noinspection PyBroadException
        try:
            control_server = TcuControlServer(simulator)
            control_server.serve()
        except KeyboardInterrupt:
            logger.debug("Shutdown requested...exiting")
        except SystemExit as exit_code:
            logger.debug("System Exit with code {}.".format(exit_code))
            sys.exit(exit_code.code)
        except Exception:
            msg = "Cannot start the Ariel TCU Control Server"
            logger.exception(msg)
            rich.print(f"[red]{msg}.")

    return 0


@app.command()
def stop():
    """Sends a `quit_server` command to the Ariel TCU Control Server."""

    with RegistryClient() as reg:
        service = reg.discover_service(SERVICE_TYPE)

        if service:
            proxy = ServiceProxy(protocol="tcp", hostname=service["host"], port=service["metadata"]["service_port"])
            proxy.quit_server()
        else:
            try:
                with TcuProxy() as tcu_proxy:
                    with tcu_proxy.get_service_proxy() as sp:
                        sp.quit_server()
            except ConnectionError:
                rich.print("[red]Couldn't connect to 'tcu_cs', process probably not running. ")


@app.command()
def status():
    """Requests the status information from the Ariel TCU Control Server."""

    if COMMANDING_PORT != 0:
        endpoint = connect_address(PROTOCOL, HOSTNAME, COMMANDING_PORT)
        port = COMMANDING_PORT
        service_port = SERVICE_PORT
        monitoring_port = MONITORING_PORT

    else:
        with RegistryClient() as reg:
            service = reg.discover_service(SERVICE_TYPE)

            if service:
                protocol = service.get("protocol", "tcp")
                hostname = service["host"]
                port = service["port"]
                service_port = service["metadata"]["service_port"]
                monitoring_port = service["metadata"]["monitoring_port"]
                endpoint = connect_address(protocol, hostname, port)
            else:
                rich.print(
                    f"[red]The Ariel TCU Control Server isn't registered as a service. The Control Server cannot be "
                    f"contacted without the required information from the service registry.[/]"
                )
                rich.print("Ariel TCU: [red]not active")
                return

    # noinspection PyUnboundLocalVariable
    if is_control_server_active(endpoint, timeout=2):
        rich.print(f"Ariel TCU: [green]active -> {endpoint}")

        with TcuProxy() as tcu:
            sim = tcu.is_simulator()
            connected = tcu.is_connected()
            ip = tcu.get_ip_address()
            rich.print(f"mode: {'simulator' if sim else 'device'}{'' if connected else ' not'} connected")
            rich.print(f"hostname: {ip}")
            # noinspection PyUnboundLocalVariable
            rich.print(f"commanding port: {port}")
            # noinspection PyUnboundLocalVariable
            rich.print(f"service port: {service_port}")
            # noinspection PyUnboundLocalVariable
            rich.print(f"monitoring port: {monitoring_port}")
    else:
        rich.print("Ariel TCU: [red]not active")


if __name__ == "__main__":
    import logging

    from egse.logger import set_all_logger_levels, remote_logging

    set_all_logger_levels(logging.DEBUG)

    sys.exit(app())
